@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,128 @@
|
|
|
1
|
+
import { createTextureAtlas, createTextureAtlasRegion } from '@flighthq/textureatlas';
|
|
2
|
+
|
|
3
|
+
import { createSpritesheet } from './spritesheet';
|
|
4
|
+
import { createSpritesheetAnimation, createSpritesheetAnimationFromFrameNames } from './spritesheetAnimation';
|
|
5
|
+
import { createSpritesheetFrame } from './spritesheetFrame';
|
|
6
|
+
|
|
7
|
+
describe('createSpritesheetAnimation', () => {
|
|
8
|
+
it('defaults direction to forward and frameDurations to null', () => {
|
|
9
|
+
const anim = createSpritesheetAnimation();
|
|
10
|
+
|
|
11
|
+
expect(anim.direction).toBe('forward');
|
|
12
|
+
expect(anim.frameDurations).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('initializes default values', () => {
|
|
16
|
+
const anim = createSpritesheetAnimation();
|
|
17
|
+
|
|
18
|
+
expect(anim.frameDuration).toBe(0);
|
|
19
|
+
expect(anim.frames).toEqual([]);
|
|
20
|
+
expect(anim.loop).toBe(false);
|
|
21
|
+
expect(anim.originX).toBe(0);
|
|
22
|
+
expect(anim.originY).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('applies partial overrides', () => {
|
|
26
|
+
const anim = createSpritesheetAnimation({ frameDuration: 100, loop: true, frames: [0, 1, 2] });
|
|
27
|
+
|
|
28
|
+
expect(anim.frameDuration).toBe(100);
|
|
29
|
+
expect(anim.loop).toBe(true);
|
|
30
|
+
expect(anim.frames).toEqual([0, 1, 2]);
|
|
31
|
+
expect(anim.originX).toBe(0);
|
|
32
|
+
expect(anim.originY).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('uses a provided frames array directly', () => {
|
|
36
|
+
const frames = [0, 1, 2];
|
|
37
|
+
const anim = createSpritesheetAnimation({ frames });
|
|
38
|
+
expect(anim.frames).toBe(frames);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('applies originX and originY overrides', () => {
|
|
42
|
+
const anim = createSpritesheetAnimation({ originX: 16, originY: 32 });
|
|
43
|
+
|
|
44
|
+
expect(anim.originX).toBe(16);
|
|
45
|
+
expect(anim.originY).toBe(32);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns a new object for each call', () => {
|
|
49
|
+
const a = createSpritesheetAnimation();
|
|
50
|
+
const b = createSpritesheetAnimation();
|
|
51
|
+
|
|
52
|
+
expect(a).not.toBe(b);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('does not share frames array reference across instances', () => {
|
|
56
|
+
const a = createSpritesheetAnimation();
|
|
57
|
+
const b = createSpritesheetAnimation();
|
|
58
|
+
a.frames.push(99);
|
|
59
|
+
|
|
60
|
+
expect(b.frames).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('createSpritesheetAnimationFromFrameNames', () => {
|
|
65
|
+
function makeSheet() {
|
|
66
|
+
const atlas = createTextureAtlas();
|
|
67
|
+
atlas.regions.push(
|
|
68
|
+
createTextureAtlasRegion({ id: 0, name: 'walk_0', x: 0, y: 0, width: 32, height: 32 }),
|
|
69
|
+
createTextureAtlasRegion({ id: 1, name: 'walk_1', x: 32, y: 0, width: 32, height: 32 }),
|
|
70
|
+
createTextureAtlasRegion({ id: 2, name: 'run_0', x: 64, y: 0, width: 32, height: 32 }),
|
|
71
|
+
createTextureAtlasRegion({ id: 3, name: null, x: 96, y: 0, width: 32, height: 32 }),
|
|
72
|
+
);
|
|
73
|
+
const sheet = createSpritesheet({ atlas });
|
|
74
|
+
for (let i = 0; i < 4; i++) {
|
|
75
|
+
sheet.frames.push(createSpritesheetFrame({ id: i }));
|
|
76
|
+
}
|
|
77
|
+
return sheet;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
it('applies options to the returned animation', () => {
|
|
81
|
+
const sheet = makeSheet();
|
|
82
|
+
const anim = createSpritesheetAnimationFromFrameNames(sheet, 'walk_', {
|
|
83
|
+
direction: 'pingpong',
|
|
84
|
+
frameDuration: 80,
|
|
85
|
+
loop: true,
|
|
86
|
+
});
|
|
87
|
+
expect(anim!.direction).toBe('pingpong');
|
|
88
|
+
expect(anim!.frameDuration).toBe(80);
|
|
89
|
+
expect(anim!.loop).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns null when no frames match the pattern', () => {
|
|
93
|
+
const sheet = makeSheet();
|
|
94
|
+
expect(createSpritesheetAnimationFromFrameNames(sheet, 'jump_')).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('returns null when spritesheet has no atlas', () => {
|
|
98
|
+
const sheet = createSpritesheet();
|
|
99
|
+
expect(createSpritesheetAnimationFromFrameNames(sheet, 'walk_')).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('selects frames matching a RegExp pattern', () => {
|
|
103
|
+
const sheet = makeSheet();
|
|
104
|
+
const anim = createSpritesheetAnimationFromFrameNames(sheet, /^walk_/);
|
|
105
|
+
expect(anim).not.toBeNull();
|
|
106
|
+
expect(anim!.frames).toEqual([0, 1]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('selects frames matching a string prefix', () => {
|
|
110
|
+
const sheet = makeSheet();
|
|
111
|
+
const anim = createSpritesheetAnimationFromFrameNames(sheet, 'walk_');
|
|
112
|
+
expect(anim).not.toBeNull();
|
|
113
|
+
expect(anim!.frames).toEqual([0, 1]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('selects frames matching an exact name', () => {
|
|
117
|
+
const sheet = makeSheet();
|
|
118
|
+
const anim = createSpritesheetAnimationFromFrameNames(sheet, 'run_0');
|
|
119
|
+
expect(anim).not.toBeNull();
|
|
120
|
+
expect(anim!.frames).toEqual([2]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('skips frames whose atlas region has a null name', () => {
|
|
124
|
+
const sheet = makeSheet();
|
|
125
|
+
const anim = createSpritesheetAnimationFromFrameNames(sheet, /./);
|
|
126
|
+
expect(anim!.frames).toEqual([0, 1, 2]);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createSpritesheetAnimationData, createSpritesheetData, createSpritesheetFrameData } from './spritesheetData';
|
|
2
|
+
|
|
3
|
+
describe('createSpritesheetAnimationData', () => {
|
|
4
|
+
it('creates with all defaults when no argument is passed', () => {
|
|
5
|
+
const anim = createSpritesheetAnimationData();
|
|
6
|
+
expect(anim.direction).toBe('forward');
|
|
7
|
+
expect(anim.frameDuration).toBe(100);
|
|
8
|
+
expect(anim.frameDurations).toBeNull();
|
|
9
|
+
expect(anim.frameNames).toEqual([]);
|
|
10
|
+
expect(anim.loop).toBe(true);
|
|
11
|
+
expect(anim.name).toBe('');
|
|
12
|
+
expect(anim.originX).toBe(0);
|
|
13
|
+
expect(anim.originY).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('applies provided partial fields', () => {
|
|
17
|
+
const anim = createSpritesheetAnimationData({
|
|
18
|
+
direction: 'pingpong',
|
|
19
|
+
frameDuration: 80,
|
|
20
|
+
frameNames: ['a', 'b', 'c'],
|
|
21
|
+
loop: false,
|
|
22
|
+
name: 'walk',
|
|
23
|
+
originX: 0.5,
|
|
24
|
+
originY: 1,
|
|
25
|
+
});
|
|
26
|
+
expect(anim.direction).toBe('pingpong');
|
|
27
|
+
expect(anim.frameDuration).toBe(80);
|
|
28
|
+
expect(anim.frameNames).toEqual(['a', 'b', 'c']);
|
|
29
|
+
expect(anim.loop).toBe(false);
|
|
30
|
+
expect(anim.name).toBe('walk');
|
|
31
|
+
expect(anim.originX).toBe(0.5);
|
|
32
|
+
expect(anim.originY).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('stores per-frame durations when provided', () => {
|
|
36
|
+
const anim = createSpritesheetAnimationData({
|
|
37
|
+
frameDurations: [100, 200, 150],
|
|
38
|
+
frameNames: ['a', 'b', 'c'],
|
|
39
|
+
});
|
|
40
|
+
expect(anim.frameDurations).toEqual([100, 200, 150]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('accepts all direction values', () => {
|
|
44
|
+
expect(createSpritesheetAnimationData({ direction: 'forward' }).direction).toBe('forward');
|
|
45
|
+
expect(createSpritesheetAnimationData({ direction: 'reverse' }).direction).toBe('reverse');
|
|
46
|
+
expect(createSpritesheetAnimationData({ direction: 'pingpong' }).direction).toBe('pingpong');
|
|
47
|
+
expect(createSpritesheetAnimationData({ direction: 'pingpong_reverse' }).direction).toBe('pingpong_reverse');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('does not share the frameNames array between instances', () => {
|
|
51
|
+
const a = createSpritesheetAnimationData();
|
|
52
|
+
const b = createSpritesheetAnimationData();
|
|
53
|
+
a.frameNames.push('x');
|
|
54
|
+
expect(b.frameNames).toHaveLength(0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('createSpritesheetData', () => {
|
|
59
|
+
it('creates with all defaults when no argument is passed', () => {
|
|
60
|
+
const data = createSpritesheetData();
|
|
61
|
+
expect(data.animations).toEqual([]);
|
|
62
|
+
expect(data.frames).toEqual([]);
|
|
63
|
+
expect(data.imageFile).toBe('');
|
|
64
|
+
expect(data.imageHeight).toBe(0);
|
|
65
|
+
expect(data.imageWidth).toBe(0);
|
|
66
|
+
expect(data.scale).toBe(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('applies provided partial fields', () => {
|
|
70
|
+
const data = createSpritesheetData({
|
|
71
|
+
imageFile: 'atlas.png',
|
|
72
|
+
imageHeight: 512,
|
|
73
|
+
imageWidth: 256,
|
|
74
|
+
scale: 2,
|
|
75
|
+
});
|
|
76
|
+
expect(data.imageFile).toBe('atlas.png');
|
|
77
|
+
expect(data.imageHeight).toBe(512);
|
|
78
|
+
expect(data.imageWidth).toBe(256);
|
|
79
|
+
expect(data.scale).toBe(2);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('stores frames and animations when provided', () => {
|
|
83
|
+
const frame = createSpritesheetFrameData({ name: 'hero' });
|
|
84
|
+
const anim = createSpritesheetAnimationData({ name: 'run' });
|
|
85
|
+
const data = createSpritesheetData({ animations: [anim], frames: [frame] });
|
|
86
|
+
expect(data.frames).toHaveLength(1);
|
|
87
|
+
expect(data.animations).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('does not share arrays between instances', () => {
|
|
91
|
+
const a = createSpritesheetData();
|
|
92
|
+
const b = createSpritesheetData();
|
|
93
|
+
a.frames.push(createSpritesheetFrameData());
|
|
94
|
+
expect(b.frames).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('createSpritesheetFrameData', () => {
|
|
99
|
+
it('creates with all defaults when no argument is passed', () => {
|
|
100
|
+
const frame = createSpritesheetFrameData();
|
|
101
|
+
expect(frame.height).toBe(0);
|
|
102
|
+
expect(frame.name).toBe('');
|
|
103
|
+
expect(frame.offsetX).toBe(0);
|
|
104
|
+
expect(frame.offsetY).toBe(0);
|
|
105
|
+
expect(frame.pivotX).toBeNull();
|
|
106
|
+
expect(frame.pivotY).toBeNull();
|
|
107
|
+
expect(frame.rotated).toBe(false);
|
|
108
|
+
expect(frame.sourceHeight).toBe(0);
|
|
109
|
+
expect(frame.sourceWidth).toBe(0);
|
|
110
|
+
expect(frame.width).toBe(0);
|
|
111
|
+
expect(frame.x).toBe(0);
|
|
112
|
+
expect(frame.y).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('applies all provided fields', () => {
|
|
116
|
+
const frame = createSpritesheetFrameData({
|
|
117
|
+
height: 64,
|
|
118
|
+
name: 'hero_idle',
|
|
119
|
+
offsetX: 2,
|
|
120
|
+
offsetY: 4,
|
|
121
|
+
pivotX: 0.5,
|
|
122
|
+
pivotY: 1,
|
|
123
|
+
rotated: true,
|
|
124
|
+
sourceHeight: 72,
|
|
125
|
+
sourceWidth: 68,
|
|
126
|
+
width: 60,
|
|
127
|
+
x: 128,
|
|
128
|
+
y: 64,
|
|
129
|
+
});
|
|
130
|
+
expect(frame.height).toBe(64);
|
|
131
|
+
expect(frame.name).toBe('hero_idle');
|
|
132
|
+
expect(frame.offsetX).toBe(2);
|
|
133
|
+
expect(frame.offsetY).toBe(4);
|
|
134
|
+
expect(frame.pivotX).toBe(0.5);
|
|
135
|
+
expect(frame.pivotY).toBe(1);
|
|
136
|
+
expect(frame.rotated).toBe(true);
|
|
137
|
+
expect(frame.sourceHeight).toBe(72);
|
|
138
|
+
expect(frame.sourceWidth).toBe(68);
|
|
139
|
+
expect(frame.width).toBe(60);
|
|
140
|
+
expect(frame.x).toBe(128);
|
|
141
|
+
expect(frame.y).toBe(64);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('accepts null pivots explicitly', () => {
|
|
145
|
+
const frame = createSpritesheetFrameData({ pivotX: null, pivotY: null });
|
|
146
|
+
expect(frame.pivotX).toBeNull();
|
|
147
|
+
expect(frame.pivotY).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createSpritesheetFrame } from './spritesheetFrame';
|
|
2
|
+
|
|
3
|
+
describe('createSpritesheetFrame', () => {
|
|
4
|
+
it('initializes default values', () => {
|
|
5
|
+
const frame = createSpritesheetFrame();
|
|
6
|
+
|
|
7
|
+
expect(frame.id).toBe(0);
|
|
8
|
+
expect(frame.offsetX).toBe(0);
|
|
9
|
+
expect(frame.offsetY).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('defaults pivotX and pivotY to null and rotated to false', () => {
|
|
13
|
+
const frame = createSpritesheetFrame();
|
|
14
|
+
|
|
15
|
+
expect(frame.pivotX).toBeNull();
|
|
16
|
+
expect(frame.pivotY).toBeNull();
|
|
17
|
+
expect(frame.rotated).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('applies partial overrides', () => {
|
|
21
|
+
const frame = createSpritesheetFrame({ id: 5, offsetX: 10, offsetY: 20 });
|
|
22
|
+
|
|
23
|
+
expect(frame.id).toBe(5);
|
|
24
|
+
expect(frame.offsetX).toBe(10);
|
|
25
|
+
expect(frame.offsetY).toBe(20);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('applies partial override for id only', () => {
|
|
29
|
+
const frame = createSpritesheetFrame({ id: 3 });
|
|
30
|
+
|
|
31
|
+
expect(frame.id).toBe(3);
|
|
32
|
+
expect(frame.offsetX).toBe(0);
|
|
33
|
+
expect(frame.offsetY).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('stores pivot and rotated values', () => {
|
|
37
|
+
const frame = createSpritesheetFrame({ id: 2, pivotX: 8, pivotY: 16, rotated: true });
|
|
38
|
+
|
|
39
|
+
expect(frame.pivotX).toBe(8);
|
|
40
|
+
expect(frame.pivotY).toBe(16);
|
|
41
|
+
expect(frame.rotated).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns a new object for each call', () => {
|
|
45
|
+
const a = createSpritesheetFrame();
|
|
46
|
+
const b = createSpritesheetFrame();
|
|
47
|
+
|
|
48
|
+
expect(a).not.toBe(b);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { createTextureAtlas, createTextureAtlasRegion } from '@flighthq/textureatlas';
|
|
2
|
+
import { buildTilesetRegions, createTileset } from '@flighthq/tileset';
|
|
3
|
+
|
|
4
|
+
import { createSpritesheetAnimationData, createSpritesheetData, createSpritesheetFrameData } from './spritesheetData';
|
|
5
|
+
import { createSpritesheetFromData, createSpritesheetFromGrid, createSpritesheetFromTileset } from './spritesheetFrom';
|
|
6
|
+
|
|
7
|
+
function makeTileset(columns: number, rows: number) {
|
|
8
|
+
const atlas = createTextureAtlas();
|
|
9
|
+
const tileset = createTileset({ atlas, columns, rows, tileWidth: 32, tileHeight: 32 });
|
|
10
|
+
buildTilesetRegions(tileset);
|
|
11
|
+
return tileset;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('createSpritesheetFromData', () => {
|
|
15
|
+
it('builds one frame per SpritesheetFrameData entry', () => {
|
|
16
|
+
const atlas = createTextureAtlas();
|
|
17
|
+
atlas.regions.push(
|
|
18
|
+
createTextureAtlasRegion({ id: 0, name: 'hero_0', x: 0, y: 0, width: 32, height: 32 }),
|
|
19
|
+
createTextureAtlasRegion({ id: 1, name: 'hero_1', x: 32, y: 0, width: 32, height: 32 }),
|
|
20
|
+
);
|
|
21
|
+
const data = createSpritesheetData({
|
|
22
|
+
frames: [
|
|
23
|
+
createSpritesheetFrameData({ name: 'hero_0', offsetX: 0, offsetY: 0 }),
|
|
24
|
+
createSpritesheetFrameData({ name: 'hero_1', offsetX: 4, offsetY: 0 }),
|
|
25
|
+
],
|
|
26
|
+
imageFile: 'hero.png',
|
|
27
|
+
imageHeight: 64,
|
|
28
|
+
imageWidth: 64,
|
|
29
|
+
});
|
|
30
|
+
const sheet = createSpritesheetFromData(data, atlas);
|
|
31
|
+
expect(sheet.frames).toHaveLength(2);
|
|
32
|
+
expect(sheet.frames[0].id).toBe(0);
|
|
33
|
+
expect(sheet.frames[1].id).toBe(1);
|
|
34
|
+
expect(sheet.frames[1].offsetX).toBe(4);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('resolves frame region IDs by name', () => {
|
|
38
|
+
const atlas = createTextureAtlas();
|
|
39
|
+
atlas.regions.push(createTextureAtlasRegion({ id: 5, name: 'walk_0', x: 0, y: 0, width: 32, height: 32 }));
|
|
40
|
+
const data = createSpritesheetData({
|
|
41
|
+
frames: [createSpritesheetFrameData({ name: 'walk_0' })],
|
|
42
|
+
imageHeight: 32,
|
|
43
|
+
imageWidth: 32,
|
|
44
|
+
});
|
|
45
|
+
const sheet = createSpritesheetFromData(data, atlas);
|
|
46
|
+
expect(sheet.frames[0].id).toBe(5);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('falls back to positional index when frame name is empty', () => {
|
|
50
|
+
const atlas = createTextureAtlas();
|
|
51
|
+
atlas.regions.push(createTextureAtlasRegion({ id: 0, name: null, x: 0, y: 0, width: 32, height: 32 }));
|
|
52
|
+
const data = createSpritesheetData({
|
|
53
|
+
frames: [createSpritesheetFrameData({ name: '' })],
|
|
54
|
+
imageHeight: 32,
|
|
55
|
+
imageWidth: 32,
|
|
56
|
+
});
|
|
57
|
+
const sheet = createSpritesheetFromData(data, atlas);
|
|
58
|
+
expect(sheet.frames[0].id).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('builds animations keyed by name with direction and frameDurations', () => {
|
|
62
|
+
const atlas = createTextureAtlas();
|
|
63
|
+
atlas.regions.push(
|
|
64
|
+
createTextureAtlasRegion({ id: 0, name: 'f0', x: 0, y: 0, width: 32, height: 32 }),
|
|
65
|
+
createTextureAtlasRegion({ id: 1, name: 'f1', x: 32, y: 0, width: 32, height: 32 }),
|
|
66
|
+
);
|
|
67
|
+
const data = createSpritesheetData({
|
|
68
|
+
animations: [
|
|
69
|
+
createSpritesheetAnimationData({
|
|
70
|
+
name: 'walk',
|
|
71
|
+
direction: 'pingpong',
|
|
72
|
+
frameDuration: 100,
|
|
73
|
+
frameDurations: [80, 120],
|
|
74
|
+
frameNames: ['f0', 'f1'],
|
|
75
|
+
loop: true,
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
frames: [createSpritesheetFrameData({ name: 'f0' }), createSpritesheetFrameData({ name: 'f1' })],
|
|
79
|
+
imageHeight: 32,
|
|
80
|
+
imageWidth: 64,
|
|
81
|
+
});
|
|
82
|
+
const sheet = createSpritesheetFromData(data, atlas);
|
|
83
|
+
const walk = sheet.animations['walk'];
|
|
84
|
+
expect(walk).toBeDefined();
|
|
85
|
+
expect(walk.direction).toBe('pingpong');
|
|
86
|
+
expect(walk.frameDurations).toEqual([80, 120]);
|
|
87
|
+
expect(walk.frames).toEqual([0, 1]);
|
|
88
|
+
expect(walk.loop).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('carries pivot and rotated onto runtime frames', () => {
|
|
92
|
+
const atlas = createTextureAtlas();
|
|
93
|
+
atlas.regions.push(createTextureAtlasRegion({ id: 0, name: 'r0', x: 0, y: 0, width: 32, height: 32 }));
|
|
94
|
+
const data = createSpritesheetData({
|
|
95
|
+
frames: [createSpritesheetFrameData({ name: 'r0', pivotX: 8, pivotY: 16, rotated: true })],
|
|
96
|
+
imageHeight: 32,
|
|
97
|
+
imageWidth: 32,
|
|
98
|
+
});
|
|
99
|
+
const sheet = createSpritesheetFromData(data, atlas);
|
|
100
|
+
expect(sheet.frames[0].pivotX).toBe(8);
|
|
101
|
+
expect(sheet.frames[0].pivotY).toBe(16);
|
|
102
|
+
expect(sheet.frames[0].rotated).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('uses all data frames when animation has no frameNames', () => {
|
|
106
|
+
const atlas = createTextureAtlas();
|
|
107
|
+
atlas.regions.push(
|
|
108
|
+
createTextureAtlasRegion({ id: 0, name: null, x: 0, y: 0, width: 32, height: 32 }),
|
|
109
|
+
createTextureAtlasRegion({ id: 1, name: null, x: 32, y: 0, width: 32, height: 32 }),
|
|
110
|
+
);
|
|
111
|
+
const data = createSpritesheetData({
|
|
112
|
+
animations: [createSpritesheetAnimationData({ name: 'idle', frameNames: [] })],
|
|
113
|
+
frames: [createSpritesheetFrameData({ name: '' }), createSpritesheetFrameData({ name: '' })],
|
|
114
|
+
imageHeight: 32,
|
|
115
|
+
imageWidth: 64,
|
|
116
|
+
});
|
|
117
|
+
const sheet = createSpritesheetFromData(data, atlas);
|
|
118
|
+
expect(sheet.animations['idle'].frames).toEqual([0, 1]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('createSpritesheetFromGrid', () => {
|
|
123
|
+
it('creates one frame per cell in row-major order', () => {
|
|
124
|
+
const sheet = createSpritesheetFromGrid({
|
|
125
|
+
columns: 4,
|
|
126
|
+
rows: 2,
|
|
127
|
+
imageFile: 'sheet.png',
|
|
128
|
+
imageWidth: 128,
|
|
129
|
+
imageHeight: 64,
|
|
130
|
+
});
|
|
131
|
+
expect(sheet.frames).toHaveLength(8);
|
|
132
|
+
expect(sheet.atlas!.regions).toHaveLength(8);
|
|
133
|
+
});
|
|
134
|
+
it('assigns sequential IDs starting at 0', () => {
|
|
135
|
+
const sheet = createSpritesheetFromGrid({
|
|
136
|
+
columns: 2,
|
|
137
|
+
rows: 1,
|
|
138
|
+
imageFile: 'sheet.png',
|
|
139
|
+
imageWidth: 64,
|
|
140
|
+
imageHeight: 32,
|
|
141
|
+
});
|
|
142
|
+
expect(sheet.frames[0].id).toBe(0);
|
|
143
|
+
expect(sheet.frames[1].id).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
it('computes cell x/y positions correctly with no margin or spacing', () => {
|
|
146
|
+
const sheet = createSpritesheetFromGrid({
|
|
147
|
+
columns: 2,
|
|
148
|
+
rows: 2,
|
|
149
|
+
imageFile: 'sheet.png',
|
|
150
|
+
imageWidth: 64,
|
|
151
|
+
imageHeight: 64,
|
|
152
|
+
});
|
|
153
|
+
const regions = sheet.atlas!.regions;
|
|
154
|
+
expect(regions[0].x).toBe(0);
|
|
155
|
+
expect(regions[0].y).toBe(0);
|
|
156
|
+
expect(regions[1].x).toBe(32);
|
|
157
|
+
expect(regions[1].y).toBe(0);
|
|
158
|
+
expect(regions[2].x).toBe(0);
|
|
159
|
+
expect(regions[2].y).toBe(32);
|
|
160
|
+
expect(regions[3].x).toBe(32);
|
|
161
|
+
expect(regions[3].y).toBe(32);
|
|
162
|
+
});
|
|
163
|
+
it('accounts for marginX, marginY, spacingX, and spacingY', () => {
|
|
164
|
+
const sheet = createSpritesheetFromGrid({
|
|
165
|
+
columns: 2,
|
|
166
|
+
rows: 1,
|
|
167
|
+
imageFile: 'sheet.png',
|
|
168
|
+
imageWidth: 74,
|
|
169
|
+
imageHeight: 36,
|
|
170
|
+
frameWidth: 32,
|
|
171
|
+
frameHeight: 32,
|
|
172
|
+
marginX: 2,
|
|
173
|
+
marginY: 2,
|
|
174
|
+
spacingX: 4,
|
|
175
|
+
spacingY: 0,
|
|
176
|
+
});
|
|
177
|
+
const regions = sheet.atlas!.regions;
|
|
178
|
+
expect(regions[0].x).toBe(2);
|
|
179
|
+
expect(regions[0].y).toBe(2);
|
|
180
|
+
expect(regions[1].x).toBe(38); // 2 + 32 + 4
|
|
181
|
+
expect(regions[1].y).toBe(2);
|
|
182
|
+
});
|
|
183
|
+
it('uses a custom namePrefix for region names', () => {
|
|
184
|
+
const sheet = createSpritesheetFromGrid({
|
|
185
|
+
columns: 2,
|
|
186
|
+
rows: 1,
|
|
187
|
+
imageFile: 'sheet.png',
|
|
188
|
+
imageWidth: 64,
|
|
189
|
+
imageHeight: 32,
|
|
190
|
+
namePrefix: 'hero_',
|
|
191
|
+
});
|
|
192
|
+
expect(sheet.atlas!.regions[0].name).toBe('hero_0');
|
|
193
|
+
expect(sheet.atlas!.regions[1].name).toBe('hero_1');
|
|
194
|
+
});
|
|
195
|
+
it('uses explicit frameWidth and frameHeight over derived values', () => {
|
|
196
|
+
const sheet = createSpritesheetFromGrid({
|
|
197
|
+
columns: 2,
|
|
198
|
+
rows: 1,
|
|
199
|
+
imageFile: 'sheet.png',
|
|
200
|
+
imageWidth: 80,
|
|
201
|
+
imageHeight: 40,
|
|
202
|
+
frameWidth: 30,
|
|
203
|
+
frameHeight: 40,
|
|
204
|
+
});
|
|
205
|
+
const regions = sheet.atlas!.regions;
|
|
206
|
+
expect(regions[0].width).toBe(30);
|
|
207
|
+
expect(regions[0].height).toBe(40);
|
|
208
|
+
});
|
|
209
|
+
it('creates a new atlas with no image (caller must assign image)', () => {
|
|
210
|
+
const sheet = createSpritesheetFromGrid({
|
|
211
|
+
columns: 1,
|
|
212
|
+
rows: 1,
|
|
213
|
+
imageFile: 'sheet.png',
|
|
214
|
+
imageWidth: 32,
|
|
215
|
+
imageHeight: 32,
|
|
216
|
+
});
|
|
217
|
+
expect(sheet.atlas!.image).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('createSpritesheetFromTileset', () => {
|
|
222
|
+
it('creates one frame per tile region', () => {
|
|
223
|
+
const tileset = makeTileset(3, 2);
|
|
224
|
+
const sheet = createSpritesheetFromTileset(tileset);
|
|
225
|
+
|
|
226
|
+
expect(sheet.frames).toHaveLength(6);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('assigns region ids to frames in order', () => {
|
|
230
|
+
const tileset = makeTileset(2, 1);
|
|
231
|
+
const sheet = createSpritesheetFromTileset(tileset);
|
|
232
|
+
const regions = tileset.atlas?.regions ?? [];
|
|
233
|
+
|
|
234
|
+
expect(sheet.frames[0].id).toBe(regions[0].id);
|
|
235
|
+
expect(sheet.frames[1].id).toBe(regions[1].id);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('passes the atlas through to the spritesheet', () => {
|
|
239
|
+
const tileset = makeTileset(1, 1);
|
|
240
|
+
const sheet = createSpritesheetFromTileset(tileset);
|
|
241
|
+
|
|
242
|
+
expect(sheet.atlas).toBe(tileset.atlas);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('produces no frames when atlas is null', () => {
|
|
246
|
+
const tileset = createTileset();
|
|
247
|
+
const sheet = createSpritesheetFromTileset(tileset);
|
|
248
|
+
|
|
249
|
+
expect(sheet.frames).toHaveLength(0);
|
|
250
|
+
expect(sheet.atlas).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('starts with no animations', () => {
|
|
254
|
+
const tileset = makeTileset(2, 2);
|
|
255
|
+
const sheet = createSpritesheetFromTileset(tileset);
|
|
256
|
+
|
|
257
|
+
expect(Object.keys(sheet.animations)).toHaveLength(0);
|
|
258
|
+
});
|
|
259
|
+
});
|