@vpmedia/phaser 1.123.0 → 1.124.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.
Files changed (206) hide show
  1. package/dist/index.js +339 -323
  2. package/dist/index.js.map +1 -1
  3. package/dist/phaser/core/animation.d.ts +3 -3
  4. package/dist/phaser/core/animation.d.ts.map +1 -1
  5. package/dist/phaser/core/animation_manager.d.ts.map +1 -1
  6. package/dist/phaser/core/animation_parser.d.ts +27 -2
  7. package/dist/phaser/core/animation_parser.d.ts.map +1 -1
  8. package/dist/phaser/core/array_set.d.ts +11 -11
  9. package/dist/phaser/core/array_set.d.ts.map +1 -1
  10. package/dist/phaser/core/cache.d.ts.map +1 -1
  11. package/dist/phaser/core/callback.d.ts.map +1 -1
  12. package/dist/phaser/core/dom.d.ts +125 -19
  13. package/dist/phaser/core/dom.d.ts.map +1 -1
  14. package/dist/phaser/core/factory.d.ts +3 -2
  15. package/dist/phaser/core/factory.d.ts.map +1 -1
  16. package/dist/phaser/core/input.d.ts +3 -2
  17. package/dist/phaser/core/input.d.ts.map +1 -1
  18. package/dist/phaser/core/input_handler.d.ts.map +1 -1
  19. package/dist/phaser/core/input_mouse.d.ts +5 -1
  20. package/dist/phaser/core/input_mouse.d.ts.map +1 -1
  21. package/dist/phaser/core/input_pointer.d.ts +3 -3
  22. package/dist/phaser/core/input_pointer.d.ts.map +1 -1
  23. package/dist/phaser/core/input_touch.d.ts +8 -8
  24. package/dist/phaser/core/input_touch.d.ts.map +1 -1
  25. package/dist/phaser/core/loader.d.ts +11 -2
  26. package/dist/phaser/core/loader.d.ts.map +1 -1
  27. package/dist/phaser/core/loader_parser.d.ts +14 -1
  28. package/dist/phaser/core/loader_parser.d.ts.map +1 -1
  29. package/dist/phaser/core/raf.d.ts +1 -1
  30. package/dist/phaser/core/raf.d.ts.map +1 -1
  31. package/dist/phaser/core/scale_manager.d.ts.map +1 -1
  32. package/dist/phaser/core/scene_manager.d.ts.map +1 -1
  33. package/dist/phaser/core/signal.d.ts.map +1 -1
  34. package/dist/phaser/core/sound.d.ts.map +1 -1
  35. package/dist/phaser/core/sound_manager.d.ts +1 -1
  36. package/dist/phaser/core/sound_manager.d.ts.map +1 -1
  37. package/dist/phaser/core/stage.d.ts +16 -6
  38. package/dist/phaser/core/stage.d.ts.map +1 -1
  39. package/dist/phaser/core/time.d.ts.map +1 -1
  40. package/dist/phaser/core/timer.d.ts +1 -1
  41. package/dist/phaser/core/timer.d.ts.map +1 -1
  42. package/dist/phaser/core/tween.d.ts +13 -5
  43. package/dist/phaser/core/tween.d.ts.map +1 -1
  44. package/dist/phaser/core/tween_data.d.ts +9 -7
  45. package/dist/phaser/core/tween_data.d.ts.map +1 -1
  46. package/dist/phaser/core/tween_manager.d.ts.map +1 -1
  47. package/dist/phaser/display/button.d.ts +6 -5
  48. package/dist/phaser/display/button.d.ts.map +1 -1
  49. package/dist/phaser/display/canvas/buffer.d.ts +1 -1
  50. package/dist/phaser/display/canvas/buffer.d.ts.map +1 -1
  51. package/dist/phaser/display/canvas/masker.d.ts +1 -1
  52. package/dist/phaser/display/canvas/masker.d.ts.map +1 -1
  53. package/dist/phaser/display/canvas/pool.d.ts +2 -2
  54. package/dist/phaser/display/canvas/pool.d.ts.map +1 -1
  55. package/dist/phaser/display/canvas/renderer.d.ts +9 -8
  56. package/dist/phaser/display/canvas/renderer.d.ts.map +1 -1
  57. package/dist/phaser/display/display_object.d.ts +3 -1
  58. package/dist/phaser/display/display_object.d.ts.map +1 -1
  59. package/dist/phaser/display/graphics.d.ts +1 -1
  60. package/dist/phaser/display/graphics.d.ts.map +1 -1
  61. package/dist/phaser/display/group.d.ts +1 -1
  62. package/dist/phaser/display/group.d.ts.map +1 -1
  63. package/dist/phaser/display/image.d.ts +6 -5
  64. package/dist/phaser/display/image.d.ts.map +1 -1
  65. package/dist/phaser/display/sprite_util.d.ts +3 -2
  66. package/dist/phaser/display/sprite_util.d.ts.map +1 -1
  67. package/dist/phaser/display/text.d.ts +2 -16
  68. package/dist/phaser/display/text.d.ts.map +1 -1
  69. package/dist/phaser/display/webgl/abstract_filter.d.ts +14 -8
  70. package/dist/phaser/display/webgl/abstract_filter.d.ts.map +1 -1
  71. package/dist/phaser/display/webgl/blend_manager.d.ts +2 -3
  72. package/dist/phaser/display/webgl/blend_manager.d.ts.map +1 -1
  73. package/dist/phaser/display/webgl/fast_sprite_batch.d.ts +11 -5
  74. package/dist/phaser/display/webgl/fast_sprite_batch.d.ts.map +1 -1
  75. package/dist/phaser/display/webgl/filter_manager.d.ts +6 -4
  76. package/dist/phaser/display/webgl/filter_manager.d.ts.map +1 -1
  77. package/dist/phaser/display/webgl/filter_texture.d.ts +4 -4
  78. package/dist/phaser/display/webgl/filter_texture.d.ts.map +1 -1
  79. package/dist/phaser/display/webgl/graphics.d.ts +2 -2
  80. package/dist/phaser/display/webgl/graphics.d.ts.map +1 -1
  81. package/dist/phaser/display/webgl/mask_manager.d.ts +3 -2
  82. package/dist/phaser/display/webgl/mask_manager.d.ts.map +1 -1
  83. package/dist/phaser/display/webgl/render_texture.d.ts +11 -8
  84. package/dist/phaser/display/webgl/render_texture.d.ts.map +1 -1
  85. package/dist/phaser/display/webgl/renderer.d.ts +8 -4
  86. package/dist/phaser/display/webgl/renderer.d.ts.map +1 -1
  87. package/dist/phaser/display/webgl/shader/normal.d.ts +28 -3
  88. package/dist/phaser/display/webgl/shader/normal.d.ts.map +1 -1
  89. package/dist/phaser/display/webgl/sprite_batch.d.ts +6 -5
  90. package/dist/phaser/display/webgl/sprite_batch.d.ts.map +1 -1
  91. package/dist/phaser/display/webgl/stencil_manager.d.ts +3 -3
  92. package/dist/phaser/display/webgl/stencil_manager.d.ts.map +1 -1
  93. package/dist/phaser/display/webgl/texture_util.d.ts +2 -2
  94. package/dist/phaser/display/webgl/texture_util.d.ts.map +1 -1
  95. package/dist/phaser/display/webgl/util.d.ts +1 -1
  96. package/dist/phaser/display/webgl/util.d.ts.map +1 -1
  97. package/dist/phaser/geom/circle.d.ts.map +1 -1
  98. package/dist/phaser/geom/line.d.ts +10 -1
  99. package/dist/phaser/geom/line.d.ts.map +1 -1
  100. package/dist/phaser/geom/rectangle.d.ts.map +1 -1
  101. package/dist/phaser/geom/util/circle.d.ts +2 -1
  102. package/dist/phaser/geom/util/circle.d.ts.map +1 -1
  103. package/dist/phaser/geom/util/point.d.ts +1 -1
  104. package/dist/phaser/geom/util/point.d.ts.map +1 -1
  105. package/dist/phaser/geom/util/rectangle.d.ts +1 -1
  106. package/dist/phaser/geom/util/rectangle.d.ts.map +1 -1
  107. package/dist/phaser/util/math.d.ts.map +1 -1
  108. package/package.json +1 -1
  109. package/src/phaser/core/animation.ts +4 -4
  110. package/src/phaser/core/animation_manager.ts +10 -10
  111. package/src/phaser/core/animation_parser.ts +30 -24
  112. package/src/phaser/core/array_set.ts +27 -27
  113. package/src/phaser/core/cache.test.ts +33 -33
  114. package/src/phaser/core/cache.ts +3 -3
  115. package/src/phaser/core/callback.ts +1 -0
  116. package/src/phaser/core/callback_context.test.ts +8 -8
  117. package/src/phaser/core/dom.ts +15 -5
  118. package/src/phaser/core/factory.ts +9 -2
  119. package/src/phaser/core/frame_data.test.ts +23 -23
  120. package/src/phaser/core/input.ts +9 -8
  121. package/src/phaser/core/input_handler.test.ts +18 -18
  122. package/src/phaser/core/input_handler.ts +1 -1
  123. package/src/phaser/core/input_mouse.ts +9 -8
  124. package/src/phaser/core/input_pointer.test.ts +26 -26
  125. package/src/phaser/core/input_pointer.ts +3 -3
  126. package/src/phaser/core/input_touch.ts +8 -8
  127. package/src/phaser/core/loader.test.ts +26 -26
  128. package/src/phaser/core/loader.ts +25 -25
  129. package/src/phaser/core/loader_parser.test.ts +16 -16
  130. package/src/phaser/core/loader_parser.ts +16 -6
  131. package/src/phaser/core/raf.ts +1 -1
  132. package/src/phaser/core/registry.test.ts +6 -6
  133. package/src/phaser/core/scale_manager.ts +2 -2
  134. package/src/phaser/core/scene_manager.test.ts +30 -30
  135. package/src/phaser/core/scene_manager.ts +1 -1
  136. package/src/phaser/core/signal.test.ts +8 -8
  137. package/src/phaser/core/signal.ts +1 -1
  138. package/src/phaser/core/signal_dispatch.test.ts +30 -30
  139. package/src/phaser/core/sound.ts +20 -8
  140. package/src/phaser/core/sound_manager.ts +6 -6
  141. package/src/phaser/core/stage.ts +19 -9
  142. package/src/phaser/core/time.ts +1 -1
  143. package/src/phaser/core/timer.test.ts +47 -47
  144. package/src/phaser/core/timer.ts +1 -1
  145. package/src/phaser/core/tween.ts +28 -25
  146. package/src/phaser/core/tween_data.test.ts +119 -0
  147. package/src/phaser/core/tween_data.ts +54 -39
  148. package/src/phaser/core/tween_easing.test.ts +5 -5
  149. package/src/phaser/core/tween_manager.ts +2 -2
  150. package/src/phaser/display/bitmap_text.test.ts +30 -30
  151. package/src/phaser/display/button.ts +8 -6
  152. package/src/phaser/display/canvas/buffer.ts +2 -2
  153. package/src/phaser/display/canvas/masker.ts +1 -1
  154. package/src/phaser/display/canvas/pool.test.ts +20 -20
  155. package/src/phaser/display/canvas/pool.ts +3 -3
  156. package/src/phaser/display/canvas/renderer.ts +12 -11
  157. package/src/phaser/display/canvas/util.test.ts +25 -25
  158. package/src/phaser/display/display_object.ts +3 -1
  159. package/src/phaser/display/graphics.test.ts +70 -20
  160. package/src/phaser/display/graphics.ts +38 -32
  161. package/src/phaser/display/group.ts +4 -4
  162. package/src/phaser/display/image.ts +28 -25
  163. package/src/phaser/display/sprite_util.ts +3 -2
  164. package/src/phaser/display/text.test.ts +28 -25
  165. package/src/phaser/display/text.ts +21 -18
  166. package/src/phaser/display/webgl/abstract_filter.ts +20 -10
  167. package/src/phaser/display/webgl/blend_manager.ts +4 -5
  168. package/src/phaser/display/webgl/earcut.test.ts +20 -20
  169. package/src/phaser/display/webgl/fast_sprite_batch.ts +21 -16
  170. package/src/phaser/display/webgl/filter_manager.ts +7 -4
  171. package/src/phaser/display/webgl/filter_texture.ts +5 -5
  172. package/src/phaser/display/webgl/graphics.ts +2 -2
  173. package/src/phaser/display/webgl/mask_manager.ts +9 -14
  174. package/src/phaser/display/webgl/render_texture.ts +15 -14
  175. package/src/phaser/display/webgl/renderer.ts +7 -6
  176. package/src/phaser/display/webgl/shader/normal.ts +41 -31
  177. package/src/phaser/display/webgl/shader/shader.test.ts +24 -24
  178. package/src/phaser/display/webgl/shader_manager.test.ts +18 -18
  179. package/src/phaser/display/webgl/sprite_batch.ts +8 -8
  180. package/src/phaser/display/webgl/stencil_manager.ts +4 -4
  181. package/src/phaser/display/webgl/texture_util.ts +2 -2
  182. package/src/phaser/display/webgl/util.ts +1 -1
  183. package/src/phaser/geom/circle.test.ts +38 -38
  184. package/src/phaser/geom/circle.ts +7 -7
  185. package/src/phaser/geom/ellipse.test.ts +12 -12
  186. package/src/phaser/geom/line.test.ts +15 -15
  187. package/src/phaser/geom/line.ts +5 -2
  188. package/src/phaser/geom/matrix.test.ts +31 -31
  189. package/src/phaser/geom/point.test.ts +31 -31
  190. package/src/phaser/geom/polygon.test.ts +27 -27
  191. package/src/phaser/geom/rectangle.test.ts +87 -87
  192. package/src/phaser/geom/rectangle.ts +11 -11
  193. package/src/phaser/geom/rounded_rectangle.test.ts +9 -9
  194. package/src/phaser/geom/util/circle.test.ts +5 -5
  195. package/src/phaser/geom/util/circle.ts +2 -1
  196. package/src/phaser/geom/util/ellipse.test.ts +2 -2
  197. package/src/phaser/geom/util/line.test.ts +2 -2
  198. package/src/phaser/geom/util/matrix.test.ts +4 -4
  199. package/src/phaser/geom/util/point.test.ts +2 -2
  200. package/src/phaser/geom/util/point.ts +1 -1
  201. package/src/phaser/geom/util/polygon.test.ts +2 -2
  202. package/src/phaser/geom/util/rectangle.test.ts +2 -2
  203. package/src/phaser/geom/util/rectangle.ts +1 -1
  204. package/src/phaser/geom/util/rounded_rectangle.test.ts +2 -2
  205. package/src/phaser/util/math.test.ts +41 -41
  206. package/src/phaser/util/math.ts +5 -2
@@ -6,6 +6,7 @@ import { TweenData } from './tween_data.js';
6
6
  import type { Game } from './game.js';
7
7
  import type { TweenManager } from './tween_manager.js';
8
8
  import type { AppliedCallback, Callback, EasingFunction, InterpolationFunction } from './callback.js';
9
+ import type { TweenValues } from './tween_data.js';
9
10
 
10
11
  export class Tween {
11
12
  public game!: Game;
@@ -23,7 +24,7 @@ export class Tween {
23
24
  public onComplete!: Signal;
24
25
  public isRunning!: boolean;
25
26
  public current!: number;
26
- public properties!: any;
27
+ public properties!: TweenValues;
27
28
  public chainedTween!: Tween | null;
28
29
  public isPaused!: boolean;
29
30
  public _onUpdateCallback!: Callback | null;
@@ -92,7 +93,7 @@ export class Tween {
92
93
  * @returns {Tween} This Tween object for chaining.
93
94
  */
94
95
  public to(
95
- properties: any,
96
+ properties: TweenValues,
96
97
  duration = 1000,
97
98
  ease: string | EasingFunction = 'Linear',
98
99
  autoStart = false,
@@ -100,13 +101,11 @@ export class Tween {
100
101
  repeat = 0,
101
102
  yoyo = false
102
103
  ): this {
103
- if (typeof ease === 'string' && this.manager.easeMap[ease]) {
104
- ease = this.manager.easeMap[ease]!;
105
- }
104
+ const easing = this.resolveEasing(ease);
106
105
  if (this.isRunning) {
107
106
  return this;
108
107
  }
109
- this.timeline.push(new TweenData(this).to(properties, duration, ease, delay, repeat, yoyo));
108
+ this.timeline.push(new TweenData(this).to(properties, duration, easing, delay, repeat, yoyo));
110
109
  if (autoStart) {
111
110
  this.start();
112
111
  }
@@ -125,7 +124,7 @@ export class Tween {
125
124
  * @returns {Tween} This Tween object for chaining.
126
125
  */
127
126
  public from(
128
- properties: any,
127
+ properties: TweenValues,
129
128
  duration = 1000,
130
129
  ease: string | EasingFunction = 'Linear',
131
130
  autoStart = false,
@@ -133,14 +132,11 @@ export class Tween {
133
132
  repeat = 0,
134
133
  yoyo = false
135
134
  ): this {
136
- if (typeof ease === 'string' && this.manager.easeMap[ease]) {
137
- ease = this.manager.easeMap[ease]!;
138
- }
139
135
  if (this.isRunning) {
140
136
  this.game.logger.warn('Tween.from cannot be called after Tween.start');
141
137
  return this;
142
138
  }
143
- this.timeline.push(new TweenData(this).from(properties, duration, ease, delay, repeat, yoyo));
139
+ this.timeline.push(new TweenData(this).from(properties, duration, this.resolveEasing(ease), delay, repeat, yoyo));
144
140
  if (autoStart) {
145
141
  this.start();
146
142
  }
@@ -161,12 +157,9 @@ export class Tween {
161
157
  // Build our master property list with the starting values
162
158
  const keys = Object.keys(tweenData.vEnd);
163
159
  for (const property of keys) {
164
- const targetProperties = this.target as unknown as Record<string, number>;
165
- this.properties[property] = targetProperties[property] ?? 0;
166
- if (!Array.isArray(this.properties[property])) {
167
- // Ensures we're using numbers, not strings
168
- this.properties[property] *= 1;
169
- }
160
+ const targetProperties = this.target as unknown as Record<string, number | string | undefined>;
161
+ // Ensures we're using numbers, not strings
162
+ this.properties[property] = Number(targetProperties[property] ?? 0);
170
163
  }
171
164
  }
172
165
  for (const tweenData of this.timeline) {
@@ -209,7 +202,7 @@ export class Tween {
209
202
  * @param {number} index - The index in the timeline to update.
210
203
  * @returns {Tween} This Tween object for chaining.
211
204
  */
212
- public updateTweenData(property: string, value: any, index = 0): this {
205
+ public updateTweenData(property: string, value: unknown, index = 0): this {
213
206
  if (this.timeline.length === 0) {
214
207
  return this;
215
208
  }
@@ -283,6 +276,19 @@ export class Tween {
283
276
  * @param {number} index - The index in the timeline to apply easing to.
284
277
  * @returns {Tween} This Tween object for chaining.
285
278
  */
279
+ public resolveEasing(ease: string | EasingFunction): EasingFunction {
280
+ if (typeof ease !== 'string') {
281
+ return ease;
282
+ }
283
+ return this.manager.easeMap[ease] ?? this.manager.easeMap['Linear']!;
284
+ }
285
+
286
+ /**
287
+ * Sets the easing function for a tween in the timeline.
288
+ * @param {string | Function} ease - The easing function or its name.
289
+ * @param {number} index - The index of the tween in the timeline.
290
+ * @returns {Tween} This Tween object for chaining.
291
+ */
286
292
  public easing(ease: string | EasingFunction, index: number): this {
287
293
  if (typeof ease === 'string' && this.manager.easeMap[ease]) {
288
294
  ease = this.manager.easeMap[ease]!;
@@ -484,7 +490,7 @@ export class Tween {
484
490
  * @param {object[]} data - The array to store the generated data in.
485
491
  * @returns {object[]} The populated data array.
486
492
  */
487
- public generateData(frameRate = 60, data: any[] = []) {
493
+ public generateData(frameRate = 60, data: Record<string, number>[] = []): Record<string, number>[] | null {
488
494
  if (this.game === null || this.target === null) {
489
495
  return null;
490
496
  }
@@ -493,12 +499,9 @@ export class Tween {
493
499
  // Build our master property list with the starting values
494
500
  const keys = Object.keys(tweenData.vEnd);
495
501
  for (const property of keys) {
496
- const targetProperties = this.target as unknown as Record<string, number>;
497
- this.properties[property] = targetProperties[property] ?? 0;
498
- if (!Array.isArray(this.properties[property])) {
499
- // Ensures we're using numbers, not strings
500
- this.properties[property] *= 1;
501
- }
502
+ const targetProperties = this.target as unknown as Record<string, number | string | undefined>;
503
+ // Ensures we're using numbers, not strings
504
+ this.properties[property] = Number(targetProperties[property] ?? 0);
502
505
  }
503
506
  }
504
507
  for (const tweenData of this.timeline) {
@@ -0,0 +1,119 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { TWEEN_COMPLETE, TWEEN_RUNNING } from './const.js';
3
+ import { Signal } from './signal.js';
4
+ import { Tween } from './tween.js';
5
+ import { TweenManager } from './tween_manager.js';
6
+ import type { Game } from './game.js';
7
+
8
+ const createGame = (): Game =>
9
+ ({
10
+ time: { time: 0, elapsedMS: 0 },
11
+ logger: { warn: (): void => undefined },
12
+ onPause: new Signal(),
13
+ onResume: new Signal(),
14
+ }) as unknown as Game;
15
+
16
+ const createTween = (target: Record<string, number>): { game: Game; tween: Tween } => {
17
+ const game = createGame();
18
+ const manager = new TweenManager(game);
19
+ return { game, tween: new Tween(target, game, manager) };
20
+ };
21
+
22
+ const advance = (game: Game, tween: Tween, ms: number): void => {
23
+ game.time.time += ms;
24
+ game.time.elapsedMS = ms;
25
+ tween.update(game.time.time);
26
+ };
27
+
28
+ describe('TweenData', (): void => {
29
+ describe('resolveEasing', (): void => {
30
+ it('takes a function as-is', (): void => {
31
+ const { tween } = createTween({ x: 0 });
32
+ const ease = (k: number): number => k * k;
33
+ expect(tween.resolveEasing(ease)).toBe(ease);
34
+ });
35
+
36
+ it('looks a named curve up in the manager', (): void => {
37
+ const { tween } = createTween({ x: 0 });
38
+ expect(tween.resolveEasing('Quad.easeIn')).toBe(tween.manager.easeMap['Quad.easeIn']);
39
+ });
40
+
41
+ it('falls back to linear for a name the manager does not know', (): void => {
42
+ const { tween } = createTween({ x: 0 });
43
+ expect(tween.resolveEasing('No.Such.Curve')).toBe(tween.manager.easeMap['Linear']);
44
+ });
45
+ });
46
+
47
+ describe('to', (): void => {
48
+ it('drives a numeric property from its start value to the end value', (): void => {
49
+ const target = { x: 0 };
50
+ const { game, tween } = createTween(target);
51
+ tween.to({ x: 100 }, 100, 'Linear', true);
52
+ advance(game, tween, 50);
53
+ expect(target.x).toBeCloseTo(50);
54
+ advance(game, tween, 50);
55
+ expect(target.x).toBeCloseTo(100);
56
+ });
57
+
58
+ it('reads a relative end value against the start', (): void => {
59
+ const target = { x: 20 };
60
+ const { game, tween } = createTween(target);
61
+ tween.to({ x: '+10' }, 100, 'Linear', true);
62
+ advance(game, tween, 100);
63
+ expect(target.x).toBeCloseTo(30);
64
+ });
65
+
66
+ it('interpolates through a list of waypoints', (): void => {
67
+ const target = { x: 0 };
68
+ const { game, tween } = createTween(target);
69
+ tween.to({ x: [0, 50, 100] }, 100, 'Linear', true);
70
+ advance(game, tween, 100);
71
+ expect(target.x).toBeCloseTo(100);
72
+ });
73
+
74
+ // An empty waypoint list has nothing to interpolate between, so the property goes to NaN.
75
+ it('survives an empty waypoint list', (): void => {
76
+ const target = { x: 7 };
77
+ const { game, tween } = createTween(target);
78
+ tween.to({ x: [] }, 100, 'Linear', true);
79
+ expect((): void => {
80
+ advance(game, tween, 100);
81
+ }).not.toThrow();
82
+ });
83
+
84
+ it('reports the tween running until the duration has passed', (): void => {
85
+ const target = { x: 0 };
86
+ const { game, tween } = createTween(target);
87
+ tween.to({ x: 10 }, 100, 'Linear', true);
88
+ game.time.time += 50;
89
+ game.time.elapsedMS = 50;
90
+ expect(tween.timeline[0]?.update(game.time.time)).toBe(TWEEN_RUNNING);
91
+ game.time.time += 100;
92
+ game.time.elapsedMS = 100;
93
+ expect(tween.timeline[0]?.update(game.time.time)).toBe(TWEEN_COMPLETE);
94
+ });
95
+ });
96
+
97
+ describe('from', (): void => {
98
+ it('starts at the given value and lands on the target original', (): void => {
99
+ const target = { x: 100 };
100
+ const { game, tween } = createTween(target);
101
+ tween.from({ x: 0 }, 100, 'Linear', true);
102
+ advance(game, tween, 0);
103
+ expect(target.x).toBeCloseTo(0);
104
+ advance(game, tween, 100);
105
+ expect(target.x).toBeCloseTo(100);
106
+ });
107
+ });
108
+
109
+ describe('generateData', (): void => {
110
+ it('produces one frame of values per step', (): void => {
111
+ const { tween } = createTween({ x: 0 });
112
+ tween.to({ x: 100 }, 100, 'Linear');
113
+ tween.start();
114
+ const data = tween.generateData(10) ?? [];
115
+ expect(data.length).toBeGreaterThan(0);
116
+ expect(data.at(-1)?.['x']).toBeCloseTo(100);
117
+ });
118
+ });
119
+ });
@@ -2,15 +2,18 @@ import type { Game } from './game.js';
2
2
  import * as MathUtils from '../util/math.js';
3
3
  import { TWEEN_COMPLETE, TWEEN_LOOPED, TWEEN_PENDING, TWEEN_RUNNING } from './const.js';
4
4
  import type { Tween } from './tween.js';
5
- import type { InterpolationFunction } from './callback.js';
5
+ import type { EasingFunction, InterpolationFunction } from './callback.js';
6
+
7
+ /** The properties a tween drives, each holding either a single target or a list of waypoints. */
8
+ export type TweenValues = Record<string, number | number[] | string>;
6
9
 
7
10
  export class TweenData {
8
11
  public parent!: Tween;
9
12
  public game!: Game;
10
- public vStart!: Record<string, any>;
11
- public vStartCache!: Record<string, any>;
12
- public vEnd!: Record<string, any>;
13
- public vEndCache!: Record<string, any>;
13
+ public vStart!: TweenValues;
14
+ public vStartCache!: TweenValues;
15
+ public vEnd!: TweenValues;
16
+ public vEndCache!: TweenValues;
14
17
  public duration!: number;
15
18
  public percent!: number;
16
19
  public value!: number;
@@ -71,7 +74,14 @@ export class TweenData {
71
74
  * @param {boolean} yoyo - Whether to reverse the tween on each repeat.
72
75
  * @returns {TweenData} This TweenData object for chaining.
73
76
  */
74
- public to(properties: any, duration: number, ease: any, delay: number, repeat: number, yoyo: boolean): this {
77
+ public to(
78
+ properties: TweenValues,
79
+ duration: number,
80
+ ease: EasingFunction,
81
+ delay: number,
82
+ repeat: number,
83
+ yoyo: boolean
84
+ ): this {
75
85
  this.vEnd = properties;
76
86
  this.duration = duration;
77
87
  this.easingFunction = ease;
@@ -92,7 +102,14 @@ export class TweenData {
92
102
  * @param {boolean} yoyo - Whether to reverse the tween on each repeat.
93
103
  * @returns {TweenData} This TweenData object for chaining.
94
104
  */
95
- public from(properties: any, duration: number, ease: any, delay: number, repeat: number, yoyo: boolean): this {
105
+ public from(
106
+ properties: TweenValues,
107
+ duration: number,
108
+ ease: EasingFunction,
109
+ delay: number,
110
+ repeat: number,
111
+ yoyo: boolean
112
+ ): this {
96
113
  this.vEnd = properties;
97
114
  this.duration = duration;
98
115
  this.easingFunction = ease;
@@ -115,9 +132,9 @@ export class TweenData {
115
132
  // Reverse them all and instant set them
116
133
  const keys = Object.keys(this.vStartCache);
117
134
  for (const property of keys) {
118
- this.vStart[property] = this.vEndCache[property];
119
- this.vEnd[property] = this.vStartCache[property];
120
- (this.parent.target as unknown as Record<string, any>)[property] = this.vStart[property];
135
+ this.vStart[property] = this.vEndCache[property]!;
136
+ this.vEnd[property] = this.vStartCache[property]!;
137
+ (this.parent.target as unknown as Record<string, unknown>)[property] = this.vStart[property];
121
138
  }
122
139
  }
123
140
  this.value = 0;
@@ -134,29 +151,27 @@ export class TweenData {
134
151
  const keys = Object.keys(this.parent.properties);
135
152
  for (const property of keys) {
136
153
  // Load the property from the parent object
137
- this.vStart[property] = this.parent.properties[property];
154
+ const start = this.parent.properties[property]!;
155
+ this.vStart[property] = start;
138
156
  // Check if an Array was provided as property value
139
- if (Array.isArray(this.vEnd[property])) {
140
- if (this.vEnd[property].length === 0) {
141
- // an empty end array leaves the property untouched for this step
142
- } else if (this.percent === 0) {
143
- // Put the start value at the beginning of the array
144
- // but we only want to do this once, if the Tween hasn't run before
145
- this.vEnd[property] = [this.vStart[property], ...this.vEnd[property]];
146
- }
157
+ const end = this.vEnd[property];
158
+ if (Array.isArray(end) && end.length > 0 && this.percent === 0) {
159
+ // Put the start value at the beginning of the array
160
+ // but we only want to do this once, if the Tween hasn't run before
161
+ this.vEnd[property] = [Number(start), ...end];
147
162
  }
148
- if (this.vEnd[property] !== undefined) {
149
- if (typeof this.vEnd[property] === 'string') {
163
+ if (end === undefined) {
164
+ // Null tween
165
+ this.vEnd[property] = start;
166
+ } else {
167
+ if (typeof end === 'string') {
150
168
  // Parses relative end values with start as base (e.g.: +10, -3)
151
- this.vEnd[property] = this.vStart[property] + Number(this.vEnd[property]);
169
+ this.vEnd[property] = Number(start) + Number(end);
152
170
  }
153
- this.parent.properties[property] = this.vEnd[property];
154
- } else {
155
- // Null tween
156
- this.vEnd[property] = this.vStart[property];
171
+ this.parent.properties[property] = this.vEnd[property]!;
157
172
  }
158
- this.vStartCache[property] = this.vStart[property];
159
- this.vEndCache[property] = this.vEnd[property];
173
+ this.vStartCache[property] = this.vStart[property]!;
174
+ this.vEndCache[property] = this.vEnd[property]!;
160
175
  }
161
176
  return this;
162
177
  }
@@ -189,11 +204,11 @@ export class TweenData {
189
204
  this.value = this.easingFunction(this.percent);
190
205
  const keys = Object.keys(this.vEnd);
191
206
  for (const property of keys) {
192
- const start = this.vStart[property];
193
207
  const end = this.vEnd[property];
194
- (this.parent.target as unknown as Record<string, any>)[property] = Array.isArray(end)
208
+ const start = Number(this.vStart[property]);
209
+ (this.parent.target as unknown as Record<string, unknown>)[property] = Array.isArray(end)
195
210
  ? this.interpolationFunction.call(this.interpolationContext, end, this.value)
196
- : start + (end - start) * this.value;
211
+ : start + (Number(end) - start) * this.value;
197
212
  }
198
213
  if ((!this.parent.reverse && this.percent === 1) || (this.parent.reverse && this.percent === 0)) {
199
214
  return this.repeat();
@@ -224,11 +239,11 @@ export class TweenData {
224
239
  const blob: Record<string, number> = {};
225
240
  const keys = Object.keys(this.vEnd);
226
241
  for (const property of keys) {
227
- const start = this.vStart[property];
228
242
  const end = this.vEnd[property];
243
+ const start = Number(this.vStart[property]);
229
244
  blob[property] = Array.isArray(end)
230
245
  ? this.interpolationFunction(end, this.value)
231
- : start + (end - start) * this.value;
246
+ : start + (Number(end) - start) * this.value;
232
247
  }
233
248
  data.push(blob);
234
249
  if ((!this.parent.reverse && this.percent === 1) || (this.parent.reverse && this.percent === 0)) {
@@ -253,8 +268,8 @@ export class TweenData {
253
268
  // Restore the properties
254
269
  const keys = Object.keys(this.vStartCache);
255
270
  for (const property of keys) {
256
- this.vStart[property] = this.vStartCache[property];
257
- this.vEnd[property] = this.vEndCache[property];
271
+ this.vStart[property] = this.vStartCache[property]!;
272
+ this.vEnd[property] = this.vEndCache[property]!;
258
273
  }
259
274
  this.inReverse = false;
260
275
  return TWEEN_COMPLETE;
@@ -267,15 +282,15 @@ export class TweenData {
267
282
  // If inReverse we're going from vEnd to vStartCache
268
283
  const keys = Object.keys(this.vStartCache);
269
284
  for (const property of keys) {
270
- this.vStart[property] = this.vEndCache[property];
271
- this.vEnd[property] = this.vStartCache[property];
285
+ this.vStart[property] = this.vEndCache[property]!;
286
+ this.vEnd[property] = this.vStartCache[property]!;
272
287
  }
273
288
  } else {
274
289
  // If not inReverse we're just repopulating the cache again
275
290
  const keys = Object.keys(this.vStartCache);
276
291
  for (const property of keys) {
277
- this.vStart[property] = this.vStartCache[property];
278
- this.vEnd[property] = this.vEndCache[property];
292
+ this.vStart[property] = this.vStartCache[property]!;
293
+ this.vEnd[property] = this.vEndCache[property]!;
279
294
  }
280
295
  // -1 means repeat forever, otherwise decrement the repeatCounter
281
296
  // We only decrement this counter if the tween isn't doing a yoyo, as that doesn't count towards the repeat total
@@ -82,22 +82,22 @@ const EXPECTED: Record<string, number[]> = {
82
82
  ],
83
83
  };
84
84
 
85
- describe('tween_easing', () => {
86
- it.each(Object.keys(EXPECTED))('%s matches its reference curve', (name) => {
85
+ describe('tween_easing', (): void => {
86
+ it.each(Object.keys(EXPECTED))('%s matches its reference curve', (name): void => {
87
87
  const fn = (Easing as unknown as Record<string, (k: number) => number>)[name]!;
88
- const actual = SAMPLE_POINTS.map((k) => fn(k));
88
+ const actual = SAMPLE_POINTS.map((k): number => fn(k));
89
89
  for (const [index, value] of actual.entries()) {
90
90
  expect(value).toBeCloseTo(EXPECTED[name]![index]!, 12);
91
91
  }
92
92
  });
93
93
 
94
- it.each(Object.keys(EXPECTED))('%s is anchored at both ends', (name) => {
94
+ it.each(Object.keys(EXPECTED))('%s is anchored at both ends', (name): void => {
95
95
  const fn = (Easing as unknown as Record<string, (k: number) => number>)[name]!;
96
96
  expect(fn(0)).toBeCloseTo(0, 12);
97
97
  expect(fn(1)).toBeCloseTo(1, 12);
98
98
  });
99
99
 
100
- it.each(Object.keys(EXPECTED))('%s does not mutate beyond its return value', (name) => {
100
+ it.each(Object.keys(EXPECTED))('%s does not mutate beyond its return value', (name): void => {
101
101
  const fn = (Easing as unknown as Record<string, (k: number) => number>)[name]!;
102
102
  for (const k of SAMPLE_POINTS) {
103
103
  expect(fn(k)).toBe(fn(k));
@@ -106,7 +106,7 @@ export class TweenManager {
106
106
  * Get all tweens managed by this manager.
107
107
  * @returns {Tween[]} An array of all active tweens.
108
108
  */
109
- public getAll() {
109
+ public getAll(): Tween[] {
110
110
  return this._tweens;
111
111
  }
112
112
 
@@ -128,7 +128,7 @@ export class TweenManager {
128
128
  */
129
129
  public removeFrom(obj: TweenTarget, children: object[] | null = null): void {
130
130
  if (Array.isArray(obj)) {
131
- for (const entry of obj) {
131
+ for (const entry of obj as object[]) {
132
132
  this.removeFrom(entry);
133
133
  }
134
134
  return;
@@ -58,35 +58,35 @@ const createGame = (font: BitmapFontData | null = FONT): Game =>
58
58
 
59
59
  const createText = (text: string, size = 10): BitmapText => new BitmapText(createGame(), 0, 0, 'test', text, size);
60
60
 
61
- describe('BitmapText', () => {
62
- beforeEach(() => {
61
+ describe('BitmapText', (): void => {
62
+ beforeEach((): void => {
63
63
  getRegistry().CACHE_MISSING_IMAGE = new Texture(atlas, new Rectangle(0, 0, 1, 1));
64
64
  });
65
65
 
66
- describe('updateText', () => {
67
- it('measures a single line from the glyph advances', () => {
66
+ describe('updateText', (): void => {
67
+ it('measures a single line from the glyph advances', (): void => {
68
68
  const bitmapText = createText('AB');
69
69
  expect(bitmapText.textWidth).toBe(20);
70
70
  expect(bitmapText.textHeight).toBe(12);
71
71
  });
72
72
 
73
- it('scales the metrics with the font size', () => {
73
+ it('scales the metrics with the font size', (): void => {
74
74
  const bitmapText = createText('AB', 20);
75
75
  expect(bitmapText.textWidth).toBe(40);
76
76
  expect(bitmapText.textHeight).toBe(24);
77
77
  });
78
78
 
79
- it('splits on a newline and takes the widest line as the width', () => {
79
+ it('splits on a newline and takes the widest line as the width', (): void => {
80
80
  const bitmapText = createText('AB\nA');
81
81
  expect(bitmapText.textWidth).toBe(20);
82
82
  expect(bitmapText.textHeight).toBe(24);
83
83
  });
84
84
 
85
- it('creates one glyph sprite per character', () => {
85
+ it('creates one glyph sprite per character', (): void => {
86
86
  expect(createText('AB')._glyphs).toHaveLength(2);
87
87
  });
88
88
 
89
- it('reuses the glyph pool when the text shrinks', () => {
89
+ it('reuses the glyph pool when the text shrinks', (): void => {
90
90
  const bitmapText = createText('AB');
91
91
  const pooled = bitmapText._glyphs;
92
92
  bitmapText.text = 'A';
@@ -95,115 +95,115 @@ describe('BitmapText', () => {
95
95
  expect(bitmapText.children).toHaveLength(1);
96
96
  });
97
97
 
98
- it('drops the glyphs that left the display list when purged', () => {
98
+ it('drops the glyphs that left the display list when purged', (): void => {
99
99
  const bitmapText = createText('AB');
100
100
  bitmapText.text = 'A';
101
101
  expect(bitmapText.purgeGlyphs()).toBe(1);
102
102
  expect(bitmapText._glyphs).toHaveLength(1);
103
103
  });
104
104
 
105
- it('lays out an empty string as a single empty line', () => {
105
+ it('lays out an empty string as a single empty line', (): void => {
106
106
  const bitmapText = createText('');
107
107
  expect(bitmapText.textWidth).toBe(0);
108
108
  expect(bitmapText.textHeight).toBe(12);
109
109
  expect(bitmapText._glyphs).toHaveLength(0);
110
110
  });
111
111
 
112
- it('does nothing when the font is not in the cache', () => {
112
+ it('does nothing when the font is not in the cache', (): void => {
113
113
  const bitmapText = new BitmapText(createGame(null), 0, 0, 'missing', 'AB');
114
114
  expect(bitmapText.textWidth).toBe(0);
115
115
  expect(bitmapText._glyphs).toHaveLength(0);
116
116
  });
117
117
  });
118
118
 
119
- describe('scanLine', () => {
120
- it('reports the whole text when nothing forces a wrap', () => {
119
+ describe('scanLine', (): void => {
120
+ it('reports the whole text when nothing forces a wrap', (): void => {
121
121
  const line = createText('AB').scanLine(FONT, 1, 'AB');
122
122
  expect(line).toMatchObject({ width: 20, text: 'AB', end: true, chars: [0, 10] });
123
123
  });
124
124
 
125
- it('stops at a newline and reports the line as unfinished', () => {
125
+ it('stops at a newline and reports the line as unfinished', (): void => {
126
126
  const line = createText('AB').scanLine(FONT, 1, 'AB\nA');
127
127
  expect(line.text).toBe('AB');
128
128
  expect(line.end).toBe(false);
129
129
  });
130
130
 
131
- it('wraps at the last space once maxWidth is exceeded', () => {
131
+ it('wraps at the last space once maxWidth is exceeded', (): void => {
132
132
  const bitmapText = createText('A A');
133
133
  bitmapText.maxWidth = 20;
134
134
  expect(bitmapText.scanLine(FONT, 1, 'A A').text).toBe('A');
135
135
  });
136
136
 
137
- it('substitutes a space for a character the font does not carry', () => {
137
+ it('substitutes a space for a character the font does not carry', (): void => {
138
138
  const line = createText('A').scanLine(FONT, 1, 'Z');
139
139
  expect(line.width).toBe(5);
140
140
  });
141
141
 
142
- it('skips a character the font cannot even substitute for', () => {
142
+ it('skips a character the font cannot even substitute for', (): void => {
143
143
  const spaceless: BitmapFontData = { ...FONT, chars: Object.fromEntries([glyph(65, 10)]) };
144
144
  expect(createText('A').scanLine(spaceless, 1, 'AZ').width).toBe(10);
145
145
  });
146
146
  });
147
147
 
148
- describe('cleanText', () => {
149
- it('drops characters the font does not carry', () => {
148
+ describe('cleanText', (): void => {
149
+ it('drops characters the font does not carry', (): void => {
150
150
  expect(createText('A').cleanText('AZB')).toBe('AB');
151
151
  });
152
152
 
153
- it('replaces them with the given stand-in', () => {
153
+ it('replaces them with the given stand-in', (): void => {
154
154
  expect(createText('A').cleanText('AZB', '?')).toBe('A?B');
155
155
  });
156
156
 
157
- it('keeps line breaks', () => {
157
+ it('keeps line breaks', (): void => {
158
158
  expect(createText('A').cleanText('A\nB')).toBe('A\nB');
159
159
  });
160
160
  });
161
161
 
162
- describe('properties', () => {
163
- it('re-lays out when the alignment changes', () => {
162
+ describe('properties', (): void => {
163
+ it('re-lays out when the alignment changes', (): void => {
164
164
  const bitmapText = createText('A\nAB');
165
165
  bitmapText.align = 'right';
166
166
  expect(bitmapText.align).toBe('right');
167
167
  expect(bitmapText._glyphs[0]?.position.x).toBe(10);
168
168
  });
169
169
 
170
- it('ignores an alignment it does not know', () => {
170
+ it('ignores an alignment it does not know', (): void => {
171
171
  const bitmapText = createText('A');
172
172
  bitmapText.align = 'sideways';
173
173
  expect(bitmapText.align).toBe('left');
174
174
  });
175
175
 
176
- it('coerces a string font size', () => {
176
+ it('coerces a string font size', (): void => {
177
177
  const bitmapText = createText('A');
178
178
  bitmapText.fontSize = '20';
179
179
  expect(bitmapText.fontSize).toBe(20);
180
180
  });
181
181
 
182
- it('ignores a font size of zero or less', () => {
182
+ it('ignores a font size of zero or less', (): void => {
183
183
  const bitmapText = createText('A');
184
184
  bitmapText.fontSize = -5;
185
185
  expect(bitmapText.fontSize).toBe(10);
186
186
  });
187
187
 
188
- it('renders the tint as a padded hex string', () => {
188
+ it('renders the tint as a padded hex string', (): void => {
189
189
  const bitmapText = createText('A');
190
190
  bitmapText.tint = 0x00ff00;
191
191
  expect(bitmapText.fill).toBe('#00FF00');
192
192
  });
193
193
 
194
- it('reads a hex string back into the tint', () => {
194
+ it('reads a hex string back into the tint', (): void => {
195
195
  const bitmapText = createText('A');
196
196
  bitmapText.fill = '#FF0000';
197
197
  expect(bitmapText.tint).toBe(0xff0000);
198
198
  });
199
199
 
200
- it('trims the font key before looking it up', () => {
200
+ it('trims the font key before looking it up', (): void => {
201
201
  const bitmapText = createText('A');
202
202
  bitmapText.font = ' other ';
203
203
  expect(bitmapText.font).toBe('other');
204
204
  });
205
205
 
206
- it('stringifies whatever the text is set to', () => {
206
+ it('stringifies whatever the text is set to', (): void => {
207
207
  const bitmapText = createText('A');
208
208
  bitmapText.text = 65;
209
209
  expect(bitmapText.text).toBe('65');