@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,60 @@
|
|
|
1
|
+
import { createDisplayContainer, getDisplayObjectRuntime } from '@flighthq/displayobject';
|
|
2
|
+
import { createImageResource } from '@flighthq/image';
|
|
3
|
+
import { addTextureAtlasRegion, createTextureAtlas } from '@flighthq/textureatlas';
|
|
4
|
+
import type { DisplayObject } from '@flighthq/types';
|
|
5
|
+
|
|
6
|
+
import { createSpritesheet } from './spritesheet';
|
|
7
|
+
import { createSpritesheetAnimation } from './spritesheetAnimation';
|
|
8
|
+
import { createSpritesheetFrame } from './spritesheetFrame';
|
|
9
|
+
import { createSpritesheetTimelineSource } from './spritesheetTimelineSource';
|
|
10
|
+
|
|
11
|
+
function makeSheet(frameCount: number) {
|
|
12
|
+
const img = document.createElement('img') as HTMLImageElement;
|
|
13
|
+
const source = createImageResource(img);
|
|
14
|
+
source.width = 128;
|
|
15
|
+
source.height = 32;
|
|
16
|
+
const atlas = createTextureAtlas({ image: source });
|
|
17
|
+
const frames = [];
|
|
18
|
+
for (let i = 0; i < frameCount; i++) {
|
|
19
|
+
addTextureAtlasRegion(atlas, i * 32, 0, 32, 32);
|
|
20
|
+
frames.push(createSpritesheetFrame({ id: i }));
|
|
21
|
+
}
|
|
22
|
+
const sheet = createSpritesheet({ atlas });
|
|
23
|
+
sheet.frames = frames;
|
|
24
|
+
return sheet;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('createSpritesheetTimelineSource', () => {
|
|
28
|
+
it('reports totalFrames from the animation and frameRate from frameDuration', () => {
|
|
29
|
+
const sheet = makeSheet(3);
|
|
30
|
+
const anim = createSpritesheetAnimation({ frameDuration: 200, frames: [0, 1, 2] });
|
|
31
|
+
|
|
32
|
+
const source = createSpritesheetTimelineSource(sheet, anim);
|
|
33
|
+
|
|
34
|
+
expect(source.totalFrames).toBe(3);
|
|
35
|
+
expect(source.frameRate).toBeCloseTo(1000 / 200);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('lazily creates one bitmap child on the target and shows the frame region', () => {
|
|
39
|
+
const sheet = makeSheet(2);
|
|
40
|
+
const anim = createSpritesheetAnimation({ frameDuration: 100, frames: [0, 1] });
|
|
41
|
+
const source = createSpritesheetTimelineSource(sheet, anim);
|
|
42
|
+
const target = createDisplayContainer();
|
|
43
|
+
|
|
44
|
+
source.constructFrame(target as DisplayObject, 1);
|
|
45
|
+
source.constructFrame(target as DisplayObject, 2);
|
|
46
|
+
|
|
47
|
+
const children = getDisplayObjectRuntime(target).children;
|
|
48
|
+
expect(children).not.toBeNull();
|
|
49
|
+
expect(children!.length).toBe(1); // reused across frames, not one-per-frame
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('does not throw when the spritesheet has no atlas', () => {
|
|
53
|
+
const sheet = createSpritesheet({ atlas: null });
|
|
54
|
+
sheet.frames = [];
|
|
55
|
+
const anim = createSpritesheetAnimation({ frameDuration: 100, frames: [0] });
|
|
56
|
+
const source = createSpritesheetTimelineSource(sheet, anim);
|
|
57
|
+
|
|
58
|
+
expect(() => source.constructFrame(createDisplayContainer() as DisplayObject, 1)).not.toThrow();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createTextureAtlas, createTextureAtlasRegion } from '@flighthq/textureatlas';
|
|
2
|
+
|
|
3
|
+
import { createSpritesheet } from './spritesheet';
|
|
4
|
+
import { createSpritesheetAnimation } from './spritesheetAnimation';
|
|
5
|
+
import { createSpritesheetAnimationData, createSpritesheetData, createSpritesheetFrameData } from './spritesheetData';
|
|
6
|
+
import { createSpritesheetFrame } from './spritesheetFrame';
|
|
7
|
+
import { validateSpritesheet, validateSpritesheetData } from './spritesheetValidation';
|
|
8
|
+
|
|
9
|
+
function makeAtlas(regionCount: number) {
|
|
10
|
+
const atlas = createTextureAtlas();
|
|
11
|
+
for (let i = 0; i < regionCount; i++) {
|
|
12
|
+
atlas.regions.push(createTextureAtlasRegion({ id: i, name: `frame_${i}`, width: 32, height: 32, x: i * 32, y: 0 }));
|
|
13
|
+
}
|
|
14
|
+
return atlas;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('validateSpritesheet', () => {
|
|
18
|
+
it('returns null for a valid spritesheet with matching atlas regions', () => {
|
|
19
|
+
const atlas = makeAtlas(3);
|
|
20
|
+
const sheet = createSpritesheet({
|
|
21
|
+
atlas,
|
|
22
|
+
frames: [createSpritesheetFrame({ id: 0 }), createSpritesheetFrame({ id: 1 }), createSpritesheetFrame({ id: 2 })],
|
|
23
|
+
animations: {
|
|
24
|
+
run: createSpritesheetAnimation({ frames: [0, 1, 2] }),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
expect(validateSpritesheet(sheet)).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it('returns null when atlas is null (no region checks possible)', () => {
|
|
30
|
+
const sheet = createSpritesheet({
|
|
31
|
+
atlas: null,
|
|
32
|
+
frames: [createSpritesheetFrame({ id: 99 })],
|
|
33
|
+
animations: {
|
|
34
|
+
idle: createSpritesheetAnimation({ frames: [0] }),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
expect(validateSpritesheet(sheet)).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it('reports an error for a frame referencing a missing atlas region', () => {
|
|
40
|
+
const atlas = makeAtlas(2); // regions 0 and 1 only
|
|
41
|
+
const sheet = createSpritesheet({
|
|
42
|
+
atlas,
|
|
43
|
+
frames: [
|
|
44
|
+
createSpritesheetFrame({ id: 0 }),
|
|
45
|
+
createSpritesheetFrame({ id: 5 }), // id 5 does not exist
|
|
46
|
+
],
|
|
47
|
+
animations: {},
|
|
48
|
+
});
|
|
49
|
+
const diagnostics = validateSpritesheet(sheet);
|
|
50
|
+
expect(diagnostics).not.toBeNull();
|
|
51
|
+
expect(diagnostics!.length).toBe(1);
|
|
52
|
+
expect(diagnostics![0].severity).toBe('error');
|
|
53
|
+
expect(diagnostics![0].frameIndex).toBe(1);
|
|
54
|
+
expect(diagnostics![0].animationName).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
it('reports an error for an animation referencing an out-of-range frame index', () => {
|
|
57
|
+
const atlas = makeAtlas(2);
|
|
58
|
+
const sheet = createSpritesheet({
|
|
59
|
+
atlas,
|
|
60
|
+
frames: [createSpritesheetFrame({ id: 0 }), createSpritesheetFrame({ id: 1 })],
|
|
61
|
+
animations: {
|
|
62
|
+
run: createSpritesheetAnimation({ frames: [0, 1, 5] }), // frame index 5 out of range
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const diagnostics = validateSpritesheet(sheet);
|
|
66
|
+
expect(diagnostics).not.toBeNull();
|
|
67
|
+
const err = diagnostics!.find((d) => d.animationName === 'run' && d.frameIndex === 2);
|
|
68
|
+
expect(err).toBeDefined();
|
|
69
|
+
expect(err!.severity).toBe('error');
|
|
70
|
+
});
|
|
71
|
+
it('reports a warning for an animation with zero frames', () => {
|
|
72
|
+
const atlas = makeAtlas(1);
|
|
73
|
+
const sheet = createSpritesheet({
|
|
74
|
+
atlas,
|
|
75
|
+
frames: [createSpritesheetFrame({ id: 0 })],
|
|
76
|
+
animations: {
|
|
77
|
+
empty: createSpritesheetAnimation({ frames: [] }),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const diagnostics = validateSpritesheet(sheet);
|
|
81
|
+
expect(diagnostics).not.toBeNull();
|
|
82
|
+
const warn = diagnostics!.find((d) => d.animationName === 'empty');
|
|
83
|
+
expect(warn).toBeDefined();
|
|
84
|
+
expect(warn!.severity).toBe('warning');
|
|
85
|
+
});
|
|
86
|
+
it('accumulates multiple diagnostics', () => {
|
|
87
|
+
const atlas = makeAtlas(1); // only region 0
|
|
88
|
+
const sheet = createSpritesheet({
|
|
89
|
+
atlas,
|
|
90
|
+
frames: [
|
|
91
|
+
createSpritesheetFrame({ id: 0 }),
|
|
92
|
+
createSpritesheetFrame({ id: 99 }), // missing region
|
|
93
|
+
],
|
|
94
|
+
animations: {
|
|
95
|
+
bad: createSpritesheetAnimation({ frames: [0, 7] }), // index 7 out of range
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const diagnostics = validateSpritesheet(sheet);
|
|
99
|
+
expect(diagnostics).not.toBeNull();
|
|
100
|
+
expect(diagnostics!.length).toBeGreaterThanOrEqual(2);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('validateSpritesheetData', () => {
|
|
105
|
+
it('returns null for valid data with matching frame names', () => {
|
|
106
|
+
const data = createSpritesheetData({
|
|
107
|
+
frames: [createSpritesheetFrameData({ name: 'walk_0' }), createSpritesheetFrameData({ name: 'walk_1' })],
|
|
108
|
+
animations: [createSpritesheetAnimationData({ name: 'walk', frameNames: ['walk_0', 'walk_1'] })],
|
|
109
|
+
});
|
|
110
|
+
expect(validateSpritesheetData(data)).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
it('returns null for data with no animations', () => {
|
|
113
|
+
const data = createSpritesheetData({ frames: [createSpritesheetFrameData({ name: 'a' })], animations: [] });
|
|
114
|
+
expect(validateSpritesheetData(data)).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
it('reports an error for an animation referencing a missing frame name', () => {
|
|
117
|
+
const data = createSpritesheetData({
|
|
118
|
+
frames: [createSpritesheetFrameData({ name: 'walk_0' })],
|
|
119
|
+
animations: [
|
|
120
|
+
createSpritesheetAnimationData({ name: 'walk', frameNames: ['walk_0', 'walk_99'] }), // walk_99 missing
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
const diagnostics = validateSpritesheetData(data);
|
|
124
|
+
expect(diagnostics).not.toBeNull();
|
|
125
|
+
const err = diagnostics!.find((d) => d.animationName === 'walk');
|
|
126
|
+
expect(err).toBeDefined();
|
|
127
|
+
expect(err!.severity).toBe('error');
|
|
128
|
+
expect(err!.message).toContain('walk_99');
|
|
129
|
+
});
|
|
130
|
+
it('reports a warning for an animation with empty frameNames', () => {
|
|
131
|
+
const data = createSpritesheetData({
|
|
132
|
+
frames: [createSpritesheetFrameData({ name: 'a' })],
|
|
133
|
+
animations: [createSpritesheetAnimationData({ name: 'run', frameNames: [] })],
|
|
134
|
+
});
|
|
135
|
+
const diagnostics = validateSpritesheetData(data);
|
|
136
|
+
expect(diagnostics).not.toBeNull();
|
|
137
|
+
expect(diagnostics![0].severity).toBe('warning');
|
|
138
|
+
expect(diagnostics![0].animationName).toBe('run');
|
|
139
|
+
});
|
|
140
|
+
it('reports a warning when frameDurations length does not match frameNames length', () => {
|
|
141
|
+
const data = createSpritesheetData({
|
|
142
|
+
frames: [createSpritesheetFrameData({ name: 'a' }), createSpritesheetFrameData({ name: 'b' })],
|
|
143
|
+
animations: [
|
|
144
|
+
createSpritesheetAnimationData({
|
|
145
|
+
name: 'run',
|
|
146
|
+
frameNames: ['a', 'b'],
|
|
147
|
+
frameDurations: [100], // length 1, but 2 frames
|
|
148
|
+
}),
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
const diagnostics = validateSpritesheetData(data);
|
|
152
|
+
expect(diagnostics).not.toBeNull();
|
|
153
|
+
const warn = diagnostics!.find((d) => d.animationName === 'run' && d.frameIndex === null);
|
|
154
|
+
expect(warn).toBeDefined();
|
|
155
|
+
expect(warn!.severity).toBe('warning');
|
|
156
|
+
});
|
|
157
|
+
it('accumulates multiple diagnostics across animations', () => {
|
|
158
|
+
const data = createSpritesheetData({
|
|
159
|
+
frames: [createSpritesheetFrameData({ name: 'a' })],
|
|
160
|
+
animations: [
|
|
161
|
+
createSpritesheetAnimationData({ name: 'anim1', frameNames: [] }), // empty → warning
|
|
162
|
+
createSpritesheetAnimationData({ name: 'anim2', frameNames: ['missing'] }), // missing → error
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
const diagnostics = validateSpritesheetData(data);
|
|
166
|
+
expect(diagnostics).not.toBeNull();
|
|
167
|
+
expect(diagnostics!.length).toBe(2);
|
|
168
|
+
});
|
|
169
|
+
});
|