@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.
Files changed (45) hide show
  1. package/dist/index.d.ts +9 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +9 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/spritesheet.d.ts +5 -0
  6. package/dist/spritesheet.d.ts.map +1 -0
  7. package/dist/spritesheet.js +28 -0
  8. package/dist/spritesheet.js.map +1 -0
  9. package/dist/spritesheetAnimation.d.ts +4 -0
  10. package/dist/spritesheetAnimation.d.ts.map +1 -0
  11. package/dist/spritesheetAnimation.js +48 -0
  12. package/dist/spritesheetAnimation.js.map +1 -0
  13. package/dist/spritesheetData.d.ts +37 -0
  14. package/dist/spritesheetData.d.ts.map +1 -0
  15. package/dist/spritesheetData.js +39 -0
  16. package/dist/spritesheetData.js.map +1 -0
  17. package/dist/spritesheetFrame.d.ts +3 -0
  18. package/dist/spritesheetFrame.d.ts.map +1 -0
  19. package/dist/spritesheetFrame.js +11 -0
  20. package/dist/spritesheetFrame.js.map +1 -0
  21. package/dist/spritesheetFrom.d.ts +6 -0
  22. package/dist/spritesheetFrom.d.ts.map +1 -0
  23. package/dist/spritesheetFrom.js +90 -0
  24. package/dist/spritesheetFrom.js.map +1 -0
  25. package/dist/spritesheetPlayer.d.ts +17 -0
  26. package/dist/spritesheetPlayer.d.ts.map +1 -0
  27. package/dist/spritesheetPlayer.js +269 -0
  28. package/dist/spritesheetPlayer.js.map +1 -0
  29. package/dist/spritesheetTimelineSource.d.ts +3 -0
  30. package/dist/spritesheetTimelineSource.d.ts.map +1 -0
  31. package/dist/spritesheetTimelineSource.js +38 -0
  32. package/dist/spritesheetTimelineSource.js.map +1 -0
  33. package/dist/spritesheetValidation.d.ts +5 -0
  34. package/dist/spritesheetValidation.d.ts.map +1 -0
  35. package/dist/spritesheetValidation.js +97 -0
  36. package/dist/spritesheetValidation.js.map +1 -0
  37. package/package.json +45 -0
  38. package/src/spritesheet.test.ts +61 -0
  39. package/src/spritesheetAnimation.test.ts +128 -0
  40. package/src/spritesheetData.test.ts +149 -0
  41. package/src/spritesheetFrame.test.ts +50 -0
  42. package/src/spritesheetFrom.test.ts +259 -0
  43. package/src/spritesheetPlayer.test.ts +661 -0
  44. package/src/spritesheetTimelineSource.test.ts +60 -0
  45. 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
+ });