@flighthq/spritesheet 0.1.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/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/spritesheet.d.ts +5 -0
- package/dist/spritesheet.d.ts.map +1 -0
- package/dist/spritesheet.js +28 -0
- package/dist/spritesheet.js.map +1 -0
- package/dist/spritesheetAnimation.d.ts +4 -0
- package/dist/spritesheetAnimation.d.ts.map +1 -0
- package/dist/spritesheetAnimation.js +48 -0
- package/dist/spritesheetAnimation.js.map +1 -0
- package/dist/spritesheetData.d.ts +37 -0
- package/dist/spritesheetData.d.ts.map +1 -0
- package/dist/spritesheetData.js +39 -0
- package/dist/spritesheetData.js.map +1 -0
- package/dist/spritesheetFrame.d.ts +3 -0
- package/dist/spritesheetFrame.d.ts.map +1 -0
- package/dist/spritesheetFrame.js +11 -0
- package/dist/spritesheetFrame.js.map +1 -0
- package/dist/spritesheetFrom.d.ts +6 -0
- package/dist/spritesheetFrom.d.ts.map +1 -0
- package/dist/spritesheetFrom.js +90 -0
- package/dist/spritesheetFrom.js.map +1 -0
- package/dist/spritesheetPlayer.d.ts +17 -0
- package/dist/spritesheetPlayer.d.ts.map +1 -0
- package/dist/spritesheetPlayer.js +269 -0
- package/dist/spritesheetPlayer.js.map +1 -0
- package/dist/spritesheetTimelineSource.d.ts +3 -0
- package/dist/spritesheetTimelineSource.d.ts.map +1 -0
- package/dist/spritesheetTimelineSource.js +38 -0
- package/dist/spritesheetTimelineSource.js.map +1 -0
- package/dist/spritesheetValidation.d.ts +5 -0
- package/dist/spritesheetValidation.d.ts.map +1 -0
- package/dist/spritesheetValidation.js +97 -0
- package/dist/spritesheetValidation.js.map +1 -0
- package/package.json +45 -0
- package/src/spritesheet.test.ts +61 -0
- package/src/spritesheetAnimation.test.ts +128 -0
- package/src/spritesheetData.test.ts +149 -0
- package/src/spritesheetFrame.test.ts +50 -0
- package/src/spritesheetFrom.test.ts +259 -0
- package/src/spritesheetPlayer.test.ts +661 -0
- package/src/spritesheetTimelineSource.test.ts +60 -0
- package/src/spritesheetValidation.test.ts +169 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals';
|
|
2
|
+
|
|
3
|
+
import { createSpritesheet } from './spritesheet';
|
|
4
|
+
import { createSpritesheetAnimation } from './spritesheetAnimation';
|
|
5
|
+
import { createSpritesheetFrame } from './spritesheetFrame';
|
|
6
|
+
import {
|
|
7
|
+
acquireSpritesheetPlayer,
|
|
8
|
+
cloneSpritesheetPlayer,
|
|
9
|
+
createSpritesheetPlayer,
|
|
10
|
+
disposeSpritesheetPlayer,
|
|
11
|
+
getSpritesheetPlayerFrame,
|
|
12
|
+
getSpritesheetPlayerFrameAt,
|
|
13
|
+
pauseSpritesheetPlayer,
|
|
14
|
+
playSpritesheetAnimation,
|
|
15
|
+
queueSpritesheetAnimation,
|
|
16
|
+
releaseSpritesheetPlayer,
|
|
17
|
+
resumeSpritesheetPlayer,
|
|
18
|
+
seekSpritesheetPlayerToFrame,
|
|
19
|
+
seekSpritesheetPlayerToTime,
|
|
20
|
+
stopSpritesheetPlayer,
|
|
21
|
+
updateSpritesheetPlayer,
|
|
22
|
+
} from './spritesheetPlayer';
|
|
23
|
+
|
|
24
|
+
function makeAnimation(frameIndices: number[], frameDuration: number, loop = true) {
|
|
25
|
+
return createSpritesheetAnimation({ frames: frameIndices, frameDuration, loop });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function makeSheet(frameCount: number, atlas = null) {
|
|
29
|
+
const sheet = createSpritesheet({ atlas });
|
|
30
|
+
for (let i = 0; i < frameCount; i++) {
|
|
31
|
+
sheet.frames.push(createSpritesheetFrame({ id: i }));
|
|
32
|
+
}
|
|
33
|
+
return sheet;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('acquireSpritesheetPlayer', () => {
|
|
37
|
+
it('returns a player in idle/complete state', () => {
|
|
38
|
+
const player = acquireSpritesheetPlayer();
|
|
39
|
+
expect(player.animation).toBeNull();
|
|
40
|
+
expect(player.complete).toBe(true);
|
|
41
|
+
expect(player.elapsed).toBe(0);
|
|
42
|
+
expect(player.frameIndex).toBe(0);
|
|
43
|
+
expect(player.paused).toBe(false);
|
|
44
|
+
expect(player.speed).toBe(1);
|
|
45
|
+
expect(player.queue).toHaveLength(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns a previously released player from the pool', () => {
|
|
49
|
+
const p = acquireSpritesheetPlayer();
|
|
50
|
+
releaseSpritesheetPlayer(p);
|
|
51
|
+
const p2 = acquireSpritesheetPlayer();
|
|
52
|
+
expect(p2).toBe(p);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('resets state on reacquire', () => {
|
|
56
|
+
const p = acquireSpritesheetPlayer();
|
|
57
|
+
const anim = makeAnimation([0, 1], 100);
|
|
58
|
+
playSpritesheetAnimation(p, anim);
|
|
59
|
+
p.elapsed = 50;
|
|
60
|
+
p.speed = 2;
|
|
61
|
+
releaseSpritesheetPlayer(p);
|
|
62
|
+
const p2 = acquireSpritesheetPlayer();
|
|
63
|
+
expect(p2.animation).toBeNull();
|
|
64
|
+
expect(p2.elapsed).toBe(0);
|
|
65
|
+
expect(p2.speed).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('cloneSpritesheetPlayer', () => {
|
|
70
|
+
it('returns a new player with copied head state', () => {
|
|
71
|
+
const player = createSpritesheetPlayer();
|
|
72
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
73
|
+
playSpritesheetAnimation(player, anim);
|
|
74
|
+
player.elapsed = 150;
|
|
75
|
+
player.frameIndex = 1;
|
|
76
|
+
const clone = cloneSpritesheetPlayer(player);
|
|
77
|
+
expect(clone).not.toBe(player);
|
|
78
|
+
expect(clone.animation).toBe(anim);
|
|
79
|
+
expect(clone.elapsed).toBe(150);
|
|
80
|
+
expect(clone.frameIndex).toBe(1);
|
|
81
|
+
expect(clone.complete).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('clone has independent signals from original', () => {
|
|
85
|
+
const player = createSpritesheetPlayer();
|
|
86
|
+
const clone = cloneSpritesheetPlayer(player);
|
|
87
|
+
expect(clone.onComplete).not.toBe(player.onComplete);
|
|
88
|
+
expect(clone.onLoop).not.toBe(player.onLoop);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('clone has an independent queue', () => {
|
|
92
|
+
const player = createSpritesheetPlayer();
|
|
93
|
+
const anim = makeAnimation([0], 100);
|
|
94
|
+
queueSpritesheetAnimation(player, anim);
|
|
95
|
+
const clone = cloneSpritesheetPlayer(player);
|
|
96
|
+
expect(clone.queue).not.toBe(player.queue);
|
|
97
|
+
expect(clone.queue).toHaveLength(1);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('createSpritesheetPlayer', () => {
|
|
102
|
+
it('starts complete with no animation', () => {
|
|
103
|
+
const player = createSpritesheetPlayer();
|
|
104
|
+
expect(player.animation).toBeNull();
|
|
105
|
+
expect(player.complete).toBe(true);
|
|
106
|
+
expect(player.elapsed).toBe(0);
|
|
107
|
+
expect(player.frameIndex).toBe(0);
|
|
108
|
+
expect(player.queue).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('uses provided queue and signals directly', () => {
|
|
112
|
+
const animation = makeAnimation([0], 100);
|
|
113
|
+
const queue = [animation];
|
|
114
|
+
const player = createSpritesheetPlayer({ queue });
|
|
115
|
+
expect(player.queue).toBe(queue);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('disposeSpritesheetPlayer', () => {
|
|
120
|
+
it('clears animation and marks complete', () => {
|
|
121
|
+
const player = createSpritesheetPlayer();
|
|
122
|
+
playSpritesheetAnimation(player, makeAnimation([0], 100));
|
|
123
|
+
disposeSpritesheetPlayer(player);
|
|
124
|
+
expect(player.animation).toBeNull();
|
|
125
|
+
expect(player.complete).toBe(true);
|
|
126
|
+
expect(player.queue).toHaveLength(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('disconnects onComplete and onLoop slots', () => {
|
|
130
|
+
const player = createSpritesheetPlayer();
|
|
131
|
+
let fired = 0;
|
|
132
|
+
connectSignal(player.onComplete, () => fired++);
|
|
133
|
+
disposeSpritesheetPlayer(player);
|
|
134
|
+
// Emitting after dispose should not fire previously connected slots.
|
|
135
|
+
player.onComplete.emit();
|
|
136
|
+
expect(fired).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('getSpritesheetPlayerFrame', () => {
|
|
141
|
+
it('returns null when no animation', () => {
|
|
142
|
+
const player = createSpritesheetPlayer();
|
|
143
|
+
const sheet = makeSheet(4);
|
|
144
|
+
expect(getSpritesheetPlayerFrame(player, sheet)).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns the correct SpritesheetFrame for current frameIndex', () => {
|
|
148
|
+
const sheet = makeSheet(4);
|
|
149
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
150
|
+
const player = createSpritesheetPlayer();
|
|
151
|
+
playSpritesheetAnimation(player, anim);
|
|
152
|
+
updateSpritesheetPlayer(player, 200);
|
|
153
|
+
const frame = getSpritesheetPlayerFrame(player, sheet);
|
|
154
|
+
expect(frame).not.toBeNull();
|
|
155
|
+
expect(frame!.id).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('returns null when the animation frame points outside the sheet', () => {
|
|
159
|
+
const sheet = makeSheet(1);
|
|
160
|
+
const anim = makeAnimation([2], 100);
|
|
161
|
+
const player = createSpritesheetPlayer();
|
|
162
|
+
playSpritesheetAnimation(player, anim);
|
|
163
|
+
expect(getSpritesheetPlayerFrame(player, sheet)).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('returns null when the current animation has no frames', () => {
|
|
167
|
+
const sheet = makeSheet(1);
|
|
168
|
+
const player = createSpritesheetPlayer();
|
|
169
|
+
playSpritesheetAnimation(player, makeAnimation([], 100));
|
|
170
|
+
expect(getSpritesheetPlayerFrame(player, sheet)).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('getSpritesheetPlayerFrameAt', () => {
|
|
175
|
+
it('returns null when no animation', () => {
|
|
176
|
+
const player = createSpritesheetPlayer();
|
|
177
|
+
expect(getSpritesheetPlayerFrameAt(player, makeSheet(4), 0)).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('returns null when animation has no frames', () => {
|
|
181
|
+
const player = createSpritesheetPlayer();
|
|
182
|
+
playSpritesheetAnimation(player, makeAnimation([], 100));
|
|
183
|
+
expect(getSpritesheetPlayerFrameAt(player, makeSheet(4), 0)).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('returns the current frame at offset 0', () => {
|
|
187
|
+
const sheet = makeSheet(4);
|
|
188
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
189
|
+
const player = createSpritesheetPlayer();
|
|
190
|
+
playSpritesheetAnimation(player, anim);
|
|
191
|
+
updateSpritesheetPlayer(player, 100); // frameIndex = 1
|
|
192
|
+
const frame = getSpritesheetPlayerFrameAt(player, sheet, 0);
|
|
193
|
+
expect(frame).not.toBeNull();
|
|
194
|
+
expect(frame!.id).toBe(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('returns the next frame at offset +1', () => {
|
|
198
|
+
const sheet = makeSheet(4);
|
|
199
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
200
|
+
const player = createSpritesheetPlayer();
|
|
201
|
+
playSpritesheetAnimation(player, anim);
|
|
202
|
+
updateSpritesheetPlayer(player, 100); // frameIndex = 1
|
|
203
|
+
const frame = getSpritesheetPlayerFrameAt(player, sheet, 1);
|
|
204
|
+
expect(frame!.id).toBe(2);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('wraps forward past the last frame', () => {
|
|
208
|
+
const sheet = makeSheet(3);
|
|
209
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
210
|
+
const player = createSpritesheetPlayer();
|
|
211
|
+
playSpritesheetAnimation(player, anim);
|
|
212
|
+
updateSpritesheetPlayer(player, 200); // frameIndex = 2
|
|
213
|
+
const frame = getSpritesheetPlayerFrameAt(player, sheet, 1);
|
|
214
|
+
expect(frame!.id).toBe(0); // wraps from frame 2 → frame 0
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('wraps backward past the first frame', () => {
|
|
218
|
+
const sheet = makeSheet(3);
|
|
219
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
220
|
+
const player = createSpritesheetPlayer();
|
|
221
|
+
playSpritesheetAnimation(player, anim);
|
|
222
|
+
// frameIndex = 0; offset -1 should wrap to frame 2
|
|
223
|
+
const frame = getSpritesheetPlayerFrameAt(player, sheet, -1);
|
|
224
|
+
expect(frame!.id).toBe(2);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('does not mutate player state', () => {
|
|
228
|
+
const sheet = makeSheet(4);
|
|
229
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
230
|
+
const player = createSpritesheetPlayer();
|
|
231
|
+
playSpritesheetAnimation(player, anim);
|
|
232
|
+
const before = { frameIndex: player.frameIndex, elapsed: player.elapsed };
|
|
233
|
+
getSpritesheetPlayerFrameAt(player, sheet, 2);
|
|
234
|
+
expect(player.frameIndex).toBe(before.frameIndex);
|
|
235
|
+
expect(player.elapsed).toBe(before.elapsed);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('pauseSpritesheetPlayer', () => {
|
|
240
|
+
it('sets paused to true', () => {
|
|
241
|
+
const player = createSpritesheetPlayer();
|
|
242
|
+
pauseSpritesheetPlayer(player);
|
|
243
|
+
expect(player.paused).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('playSpritesheetAnimation', () => {
|
|
248
|
+
it('sets animation and resets state', () => {
|
|
249
|
+
const player = createSpritesheetPlayer();
|
|
250
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
251
|
+
playSpritesheetAnimation(player, anim);
|
|
252
|
+
expect(player.animation).toBe(anim);
|
|
253
|
+
expect(player.elapsed).toBe(0);
|
|
254
|
+
expect(player.frameIndex).toBe(0);
|
|
255
|
+
expect(player.complete).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('clears the queue', () => {
|
|
259
|
+
const player = createSpritesheetPlayer();
|
|
260
|
+
const anim = makeAnimation([0, 1], 100);
|
|
261
|
+
const queued = makeAnimation([2, 3], 100);
|
|
262
|
+
playSpritesheetAnimation(player, anim);
|
|
263
|
+
queueSpritesheetAnimation(player, queued);
|
|
264
|
+
playSpritesheetAnimation(player, anim);
|
|
265
|
+
expect(player.queue).toEqual([]);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('does not restart if same animation and restart=false', () => {
|
|
269
|
+
const player = createSpritesheetPlayer();
|
|
270
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
271
|
+
playSpritesheetAnimation(player, anim);
|
|
272
|
+
player.elapsed = 150;
|
|
273
|
+
playSpritesheetAnimation(player, anim, false);
|
|
274
|
+
expect(player.elapsed).toBe(150);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('restarts if same animation and restart=true', () => {
|
|
278
|
+
const player = createSpritesheetPlayer();
|
|
279
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
280
|
+
playSpritesheetAnimation(player, anim);
|
|
281
|
+
player.elapsed = 150;
|
|
282
|
+
playSpritesheetAnimation(player, anim, true);
|
|
283
|
+
expect(player.elapsed).toBe(0);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('clears animation and marks complete when animation is null', () => {
|
|
287
|
+
const player = createSpritesheetPlayer();
|
|
288
|
+
const anim = makeAnimation([0, 1], 100);
|
|
289
|
+
playSpritesheetAnimation(player, anim);
|
|
290
|
+
queueSpritesheetAnimation(player, makeAnimation([2], 100));
|
|
291
|
+
playSpritesheetAnimation(player, null);
|
|
292
|
+
expect(player.animation).toBeNull();
|
|
293
|
+
expect(player.complete).toBe(true);
|
|
294
|
+
expect(player.elapsed).toBe(0);
|
|
295
|
+
expect(player.frameIndex).toBe(0);
|
|
296
|
+
expect(player.queue).toEqual([]);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('queueSpritesheetAnimation', () => {
|
|
301
|
+
it('appends animation to the queue', () => {
|
|
302
|
+
const player = createSpritesheetPlayer();
|
|
303
|
+
const anim = makeAnimation([0, 1], 100);
|
|
304
|
+
queueSpritesheetAnimation(player, anim);
|
|
305
|
+
expect(player.queue).toHaveLength(1);
|
|
306
|
+
expect(player.queue[0]).toBe(anim);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('appends multiple animations in order', () => {
|
|
310
|
+
const player = createSpritesheetPlayer();
|
|
311
|
+
const a1 = makeAnimation([0], 100);
|
|
312
|
+
const a2 = makeAnimation([1], 100);
|
|
313
|
+
queueSpritesheetAnimation(player, a1);
|
|
314
|
+
queueSpritesheetAnimation(player, a2);
|
|
315
|
+
expect(player.queue[0]).toBe(a1);
|
|
316
|
+
expect(player.queue[1]).toBe(a2);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('releaseSpritesheetPlayer', () => {
|
|
321
|
+
it('resets all fields to idle state', () => {
|
|
322
|
+
const player = createSpritesheetPlayer();
|
|
323
|
+
const anim = makeAnimation([0, 1], 100);
|
|
324
|
+
playSpritesheetAnimation(player, anim);
|
|
325
|
+
player.elapsed = 80;
|
|
326
|
+
player.speed = 3;
|
|
327
|
+
player.paused = true;
|
|
328
|
+
releaseSpritesheetPlayer(player);
|
|
329
|
+
expect(player.animation).toBeNull();
|
|
330
|
+
expect(player.complete).toBe(true);
|
|
331
|
+
expect(player.elapsed).toBe(0);
|
|
332
|
+
expect(player.frameIndex).toBe(0);
|
|
333
|
+
expect(player.paused).toBe(false);
|
|
334
|
+
expect(player.speed).toBe(1);
|
|
335
|
+
expect(player.queue).toHaveLength(0);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('disconnects signals so they do not fire after release', () => {
|
|
339
|
+
const player = createSpritesheetPlayer();
|
|
340
|
+
let fired = 0;
|
|
341
|
+
connectSignal(player.onComplete, () => fired++);
|
|
342
|
+
releaseSpritesheetPlayer(player);
|
|
343
|
+
player.onComplete.emit();
|
|
344
|
+
expect(fired).toBe(0);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('resumeSpritesheetPlayer', () => {
|
|
349
|
+
it('sets paused to false', () => {
|
|
350
|
+
const player = createSpritesheetPlayer({ paused: true });
|
|
351
|
+
resumeSpritesheetPlayer(player);
|
|
352
|
+
expect(player.paused).toBe(false);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('seekSpritesheetPlayerToFrame', () => {
|
|
357
|
+
it('sets frameIndex to the target frame', () => {
|
|
358
|
+
const player = createSpritesheetPlayer();
|
|
359
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
360
|
+
playSpritesheetAnimation(player, anim);
|
|
361
|
+
seekSpritesheetPlayerToFrame(player, 2);
|
|
362
|
+
expect(player.frameIndex).toBe(2);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('clamps negative frame index to 0', () => {
|
|
366
|
+
const player = createSpritesheetPlayer();
|
|
367
|
+
playSpritesheetAnimation(player, makeAnimation([0, 1, 2], 100));
|
|
368
|
+
seekSpritesheetPlayerToFrame(player, -1);
|
|
369
|
+
expect(player.frameIndex).toBe(0);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('clamps frame index beyond last frame', () => {
|
|
373
|
+
const player = createSpritesheetPlayer();
|
|
374
|
+
playSpritesheetAnimation(player, makeAnimation([0, 1, 2], 100));
|
|
375
|
+
seekSpritesheetPlayerToFrame(player, 99);
|
|
376
|
+
expect(player.frameIndex).toBe(2);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('is a no-op when no animation is set', () => {
|
|
380
|
+
const player = createSpritesheetPlayer();
|
|
381
|
+
seekSpritesheetPlayerToFrame(player, 2);
|
|
382
|
+
expect(player.frameIndex).toBe(0);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe('seekSpritesheetPlayerToTime', () => {
|
|
387
|
+
it('sets elapsed to the given time', () => {
|
|
388
|
+
const player = createSpritesheetPlayer();
|
|
389
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
390
|
+
playSpritesheetAnimation(player, anim);
|
|
391
|
+
seekSpritesheetPlayerToTime(player, 250);
|
|
392
|
+
expect(player.elapsed).toBe(250);
|
|
393
|
+
expect(player.frameIndex).toBe(2);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('clamps to 0', () => {
|
|
397
|
+
const player = createSpritesheetPlayer();
|
|
398
|
+
playSpritesheetAnimation(player, makeAnimation([0, 1, 2], 100));
|
|
399
|
+
seekSpritesheetPlayerToTime(player, -50);
|
|
400
|
+
expect(player.elapsed).toBe(0);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('is a no-op when no animation is set', () => {
|
|
404
|
+
const player = createSpritesheetPlayer();
|
|
405
|
+
seekSpritesheetPlayerToTime(player, 100);
|
|
406
|
+
expect(player.elapsed).toBe(0);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe('stopSpritesheetPlayer', () => {
|
|
411
|
+
it('resets elapsed, frameIndex, and marks complete', () => {
|
|
412
|
+
const player = createSpritesheetPlayer();
|
|
413
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
414
|
+
const queued = makeAnimation([3], 100);
|
|
415
|
+
playSpritesheetAnimation(player, anim);
|
|
416
|
+
queueSpritesheetAnimation(player, queued);
|
|
417
|
+
player.elapsed = 150;
|
|
418
|
+
player.frameIndex = 1;
|
|
419
|
+
stopSpritesheetPlayer(player);
|
|
420
|
+
expect(player.elapsed).toBe(0);
|
|
421
|
+
expect(player.frameIndex).toBe(0);
|
|
422
|
+
expect(player.complete).toBe(true);
|
|
423
|
+
expect(player.queue).toHaveLength(0);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
describe('updateSpritesheetPlayer', () => {
|
|
428
|
+
it('returns false when no animation', () => {
|
|
429
|
+
const player = createSpritesheetPlayer();
|
|
430
|
+
expect(updateSpritesheetPlayer(player, 16)).toBe(false);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('returns false for an animation with no frames', () => {
|
|
434
|
+
const player = createSpritesheetPlayer();
|
|
435
|
+
playSpritesheetAnimation(player, makeAnimation([], 100));
|
|
436
|
+
expect(updateSpritesheetPlayer(player, 16)).toBe(false);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('advances elapsed time', () => {
|
|
440
|
+
const player = createSpritesheetPlayer();
|
|
441
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
442
|
+
playSpritesheetAnimation(player, anim);
|
|
443
|
+
updateSpritesheetPlayer(player, 50);
|
|
444
|
+
expect(player.elapsed).toBe(50);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('selects correct frame index from elapsed time', () => {
|
|
448
|
+
const player = createSpritesheetPlayer();
|
|
449
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
450
|
+
playSpritesheetAnimation(player, anim);
|
|
451
|
+
updateSpritesheetPlayer(player, 0);
|
|
452
|
+
expect(player.frameIndex).toBe(0);
|
|
453
|
+
updateSpritesheetPlayer(player, 100);
|
|
454
|
+
expect(player.frameIndex).toBe(1);
|
|
455
|
+
updateSpritesheetPlayer(player, 100);
|
|
456
|
+
expect(player.frameIndex).toBe(2);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('loops back to frame 0 after full loop', () => {
|
|
460
|
+
const player = createSpritesheetPlayer();
|
|
461
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
462
|
+
playSpritesheetAnimation(player, anim);
|
|
463
|
+
updateSpritesheetPlayer(player, 400);
|
|
464
|
+
expect(player.frameIndex).toBe(0);
|
|
465
|
+
expect(player.complete).toBe(false);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('clamps to last frame and marks complete for non-looping animation', () => {
|
|
469
|
+
const player = createSpritesheetPlayer();
|
|
470
|
+
const anim = makeAnimation([0, 1, 2, 3], 100, false);
|
|
471
|
+
playSpritesheetAnimation(player, anim);
|
|
472
|
+
updateSpritesheetPlayer(player, 500);
|
|
473
|
+
expect(player.frameIndex).toBe(3);
|
|
474
|
+
expect(player.complete).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('returns false after non-looping animation completes', () => {
|
|
478
|
+
const player = createSpritesheetPlayer();
|
|
479
|
+
const anim = makeAnimation([0, 1], 100, false);
|
|
480
|
+
playSpritesheetAnimation(player, anim);
|
|
481
|
+
updateSpritesheetPlayer(player, 300);
|
|
482
|
+
expect(updateSpritesheetPlayer(player, 100)).toBe(false);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('advances to queued animation when non-looping animation completes', () => {
|
|
486
|
+
const player = createSpritesheetPlayer();
|
|
487
|
+
const first = makeAnimation([0, 1], 100, false);
|
|
488
|
+
const second = makeAnimation([2, 3], 100, false);
|
|
489
|
+
playSpritesheetAnimation(player, first);
|
|
490
|
+
queueSpritesheetAnimation(player, second);
|
|
491
|
+
updateSpritesheetPlayer(player, 300);
|
|
492
|
+
expect(player.animation).toBe(second);
|
|
493
|
+
expect(player.elapsed).toBe(0);
|
|
494
|
+
expect(player.frameIndex).toBe(0);
|
|
495
|
+
expect(player.complete).toBe(false);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('does not emit onComplete when advancing to a queued animation', () => {
|
|
499
|
+
const player = createSpritesheetPlayer();
|
|
500
|
+
const first = makeAnimation([0], 100, false);
|
|
501
|
+
const second = makeAnimation([1], 100, false);
|
|
502
|
+
let fired = 0;
|
|
503
|
+
connectSignal(player.onComplete, () => fired++);
|
|
504
|
+
playSpritesheetAnimation(player, first);
|
|
505
|
+
queueSpritesheetAnimation(player, second);
|
|
506
|
+
updateSpritesheetPlayer(player, 200);
|
|
507
|
+
expect(player.animation).toBe(second);
|
|
508
|
+
expect(fired).toBe(0);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('plays through multiple queued animations in order', () => {
|
|
512
|
+
const player = createSpritesheetPlayer();
|
|
513
|
+
const first = makeAnimation([0], 100, false);
|
|
514
|
+
const second = makeAnimation([1], 100, false);
|
|
515
|
+
const third = makeAnimation([2], 100, false);
|
|
516
|
+
playSpritesheetAnimation(player, first);
|
|
517
|
+
queueSpritesheetAnimation(player, second);
|
|
518
|
+
queueSpritesheetAnimation(player, third);
|
|
519
|
+
updateSpritesheetPlayer(player, 200);
|
|
520
|
+
expect(player.animation).toBe(second);
|
|
521
|
+
updateSpritesheetPlayer(player, 200);
|
|
522
|
+
expect(player.animation).toBe(third);
|
|
523
|
+
updateSpritesheetPlayer(player, 200);
|
|
524
|
+
expect(player.complete).toBe(true);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('emits onComplete when non-looping animation finishes', () => {
|
|
528
|
+
const player = createSpritesheetPlayer();
|
|
529
|
+
const anim = makeAnimation([0, 1, 2], 100, false);
|
|
530
|
+
playSpritesheetAnimation(player, anim);
|
|
531
|
+
let fired = 0;
|
|
532
|
+
connectSignal(player.onComplete, () => fired++);
|
|
533
|
+
updateSpritesheetPlayer(player, 400);
|
|
534
|
+
expect(fired).toBe(1);
|
|
535
|
+
updateSpritesheetPlayer(player, 100);
|
|
536
|
+
expect(fired).toBe(1);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('does not emit onComplete for looping animation', () => {
|
|
540
|
+
const player = createSpritesheetPlayer();
|
|
541
|
+
const anim = makeAnimation([0, 1, 2], 100, true);
|
|
542
|
+
playSpritesheetAnimation(player, anim);
|
|
543
|
+
let fired = 0;
|
|
544
|
+
connectSignal(player.onComplete, () => fired++);
|
|
545
|
+
updateSpritesheetPlayer(player, 400);
|
|
546
|
+
expect(fired).toBe(0);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('emits onLoop each time a looping animation cycles', () => {
|
|
550
|
+
const player = createSpritesheetPlayer();
|
|
551
|
+
const anim = makeAnimation([0, 1, 2, 3], 100, true);
|
|
552
|
+
playSpritesheetAnimation(player, anim);
|
|
553
|
+
let loops = 0;
|
|
554
|
+
connectSignal(player.onLoop, () => loops++);
|
|
555
|
+
updateSpritesheetPlayer(player, 400);
|
|
556
|
+
expect(loops).toBe(1);
|
|
557
|
+
updateSpritesheetPlayer(player, 400);
|
|
558
|
+
expect(loops).toBe(2);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('does not emit onLoop for non-looping animation', () => {
|
|
562
|
+
const player = createSpritesheetPlayer();
|
|
563
|
+
const anim = makeAnimation([0, 1, 2, 3], 100, false);
|
|
564
|
+
playSpritesheetAnimation(player, anim);
|
|
565
|
+
let loops = 0;
|
|
566
|
+
connectSignal(player.onLoop, () => loops++);
|
|
567
|
+
updateSpritesheetPlayer(player, 500);
|
|
568
|
+
expect(loops).toBe(0);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('does not advance queue for looping animation', () => {
|
|
572
|
+
const player = createSpritesheetPlayer();
|
|
573
|
+
const looping = makeAnimation([0, 1], 100, true);
|
|
574
|
+
const queued = makeAnimation([2, 3], 100, false);
|
|
575
|
+
playSpritesheetAnimation(player, looping);
|
|
576
|
+
queueSpritesheetAnimation(player, queued);
|
|
577
|
+
updateSpritesheetPlayer(player, 500);
|
|
578
|
+
expect(player.animation).toBe(looping);
|
|
579
|
+
expect(player.queue).toHaveLength(1);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('does not advance elapsed when paused', () => {
|
|
583
|
+
const player = createSpritesheetPlayer();
|
|
584
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
585
|
+
playSpritesheetAnimation(player, anim);
|
|
586
|
+
pauseSpritesheetPlayer(player);
|
|
587
|
+
const result = updateSpritesheetPlayer(player, 200);
|
|
588
|
+
expect(result).toBe(false);
|
|
589
|
+
expect(player.elapsed).toBe(0);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('advances elapsed again after resumeSpritesheetPlayer', () => {
|
|
593
|
+
const player = createSpritesheetPlayer();
|
|
594
|
+
const anim = makeAnimation([0, 1, 2], 100);
|
|
595
|
+
playSpritesheetAnimation(player, anim);
|
|
596
|
+
pauseSpritesheetPlayer(player);
|
|
597
|
+
updateSpritesheetPlayer(player, 200);
|
|
598
|
+
resumeSpritesheetPlayer(player);
|
|
599
|
+
updateSpritesheetPlayer(player, 100);
|
|
600
|
+
expect(player.elapsed).toBe(100);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('scales elapsed time by speed', () => {
|
|
604
|
+
const player = createSpritesheetPlayer({ speed: 2 });
|
|
605
|
+
const anim = makeAnimation([0, 1, 2, 3], 100);
|
|
606
|
+
playSpritesheetAnimation(player, anim);
|
|
607
|
+
updateSpritesheetPlayer(player, 100);
|
|
608
|
+
expect(player.elapsed).toBe(200);
|
|
609
|
+
expect(player.frameIndex).toBe(2);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('plays reverse direction in reverse frame order', () => {
|
|
613
|
+
const player = createSpritesheetPlayer();
|
|
614
|
+
const anim = createSpritesheetAnimation({
|
|
615
|
+
direction: 'reverse',
|
|
616
|
+
frameDuration: 100,
|
|
617
|
+
frames: [0, 1, 2, 3],
|
|
618
|
+
loop: true,
|
|
619
|
+
});
|
|
620
|
+
playSpritesheetAnimation(player, anim);
|
|
621
|
+
updateSpritesheetPlayer(player, 0);
|
|
622
|
+
expect(player.frameIndex).toBe(3);
|
|
623
|
+
updateSpritesheetPlayer(player, 100);
|
|
624
|
+
expect(player.frameIndex).toBe(2);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('plays pingpong direction forward then backward', () => {
|
|
628
|
+
const player = createSpritesheetPlayer();
|
|
629
|
+
const anim = createSpritesheetAnimation({
|
|
630
|
+
direction: 'pingpong',
|
|
631
|
+
frameDuration: 100,
|
|
632
|
+
frames: [0, 1, 2],
|
|
633
|
+
loop: true,
|
|
634
|
+
});
|
|
635
|
+
playSpritesheetAnimation(player, anim);
|
|
636
|
+
// 6 virtual frames (0,1,2,1 wraps) at 100ms each = 600ms loop
|
|
637
|
+
updateSpritesheetPlayer(player, 0);
|
|
638
|
+
expect(player.frameIndex).toBe(0);
|
|
639
|
+
updateSpritesheetPlayer(player, 100);
|
|
640
|
+
expect(player.frameIndex).toBe(1);
|
|
641
|
+
updateSpritesheetPlayer(player, 100);
|
|
642
|
+
expect(player.frameIndex).toBe(2);
|
|
643
|
+
updateSpritesheetPlayer(player, 100);
|
|
644
|
+
expect(player.frameIndex).toBe(1);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('uses per-frame durations when frameDurations is set', () => {
|
|
648
|
+
const player = createSpritesheetPlayer();
|
|
649
|
+
const anim = createSpritesheetAnimation({
|
|
650
|
+
frameDuration: 100,
|
|
651
|
+
frameDurations: [50, 200, 150],
|
|
652
|
+
frames: [0, 1, 2],
|
|
653
|
+
loop: true,
|
|
654
|
+
});
|
|
655
|
+
playSpritesheetAnimation(player, anim);
|
|
656
|
+
updateSpritesheetPlayer(player, 50);
|
|
657
|
+
expect(player.frameIndex).toBe(1); // 50ms into frame 1 (50..250)
|
|
658
|
+
updateSpritesheetPlayer(player, 200);
|
|
659
|
+
expect(player.frameIndex).toBe(2); // 250ms into frame 2 (250..400)
|
|
660
|
+
});
|
|
661
|
+
});
|