@spritebuddy/core 0.0.0-stage → 0.2.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/LICENSE +15 -0
- package/README.md +60 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +14 -0
- package/dist/parse.js +178 -0
- package/dist/parse.js.map +1 -0
- package/dist/player.d.ts +53 -0
- package/dist/player.js +116 -0
- package/dist/player.js.map +1 -0
- package/dist/resolve.d.ts +30 -0
- package/dist/resolve.js +50 -0
- package/dist/resolve.js.map +1 -0
- package/dist/timing.d.ts +33 -0
- package/dist/timing.js +74 -0
- package/dist/timing.js.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -4
- package/schema.json +161 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Hammond
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @spritebuddy/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reads [SpriteBuddy](https://github.com/Patrick-Hammond/spritebuddy) runtime files
|
|
4
|
+
([format](https://github.com/Patrick-Hammond/spritebuddy-runtimes/blob/main/docs/format.md)) and works out what to draw at any moment. No
|
|
5
|
+
dependencies and no engine: [`@spritebuddy/pixi`](https://www.npmjs.com/package/@spritebuddy/pixi)
|
|
6
|
+
builds on it, and you can use it with any renderer that can draw part of an image.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install @spritebuddy/core
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { parseFile, Player } from '@spritebuddy/core';
|
|
16
|
+
|
|
17
|
+
const file = parseFile(await (await fetch('knight.anims.json')).json());
|
|
18
|
+
const player = new Player(file, {
|
|
19
|
+
onComplete: () => player.play('idle'),
|
|
20
|
+
onEvent: (name) => name === 'hit' && dealDamage(), // events tagged on frames in SpriteBuddy
|
|
21
|
+
}).play('attack');
|
|
22
|
+
|
|
23
|
+
// every tick
|
|
24
|
+
player.update(deltaMs);
|
|
25
|
+
for (const part of player.parts) {
|
|
26
|
+
// Draw part.frame.rect from the sheet with its full-frame top-left at
|
|
27
|
+
// (spriteX + part.x, spriteY + part.y), mirrored by part.flipX / part.flipY, at
|
|
28
|
+
// part.opacity. Trimmed frames start part.frame.trim.x/y further in; rotated frames are
|
|
29
|
+
// stored turned 90° clockwise.
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The [canvas example](https://github.com/Patrick-Hammond/spritebuddy-runtimes/tree/main/examples/canvas) is a complete page that does exactly
|
|
34
|
+
this with a 2D canvas, rotation, trim and flips included, in about 60 lines.
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
- `parseFile(json)` checks a runtime file and fills in every default. It throws a
|
|
39
|
+
`FormatError` that says where the file is wrong, and refuses versions newer than `VERSION`.
|
|
40
|
+
- `new Player(file, { onFrame, onEvent, onComplete })` keeps time for one sprite:
|
|
41
|
+
- `play(name, fromMs?)` starts an animation (it throws for an unknown name, listing the
|
|
42
|
+
names there are), `stop()`, `seek(ms)` and `update(ms)`.
|
|
43
|
+
- `speed` (1 is normal), `playing`, `names`, `animation(name)`.
|
|
44
|
+
- `parts`: what to draw now, bottom to top. `state` adds the animation, frame index, time
|
|
45
|
+
and whether a `once` animation is done.
|
|
46
|
+
- `onFrame(index)` fires when the frame changes; `onEvent(name, index)` for each event
|
|
47
|
+
tagged on a frame as it starts showing (after `onFrame`), including frames a long tick
|
|
48
|
+
skipped; `onComplete()` when a `once` animation ends. `play` fires the first frame's
|
|
49
|
+
events; `seek` fires none.
|
|
50
|
+
- The pure functions underneath, for engines that keep their own clock:
|
|
51
|
+
- `frameAt(animation, ms)` gives the frame index showing at a time, and whether it is done.
|
|
52
|
+
- `partsOf(file, animationFrame)` gives the parts to draw for one frame.
|
|
53
|
+
- `eventsBetween(animation, fromMs, toMs)` lists the events of frames starting after `fromMs`
|
|
54
|
+
up to `toMs`, at most one cycle's worth.
|
|
55
|
+
- `boundsOf`, `cycleDuration` and `cycleOrder` help with layout and timelines.
|
|
56
|
+
- The JSON Schema ships as `@spritebuddy/core/schema.json`.
|
|
57
|
+
|
|
58
|
+
Every function is tested against the shared
|
|
59
|
+
[golden tables](https://github.com/Patrick-Hammond/spritebuddy-runtimes/tree/main/fixtures), the same ones the PixiJS and Godot runtimes pass.
|
|
60
|
+
|
|
61
|
+
Licence: ISC.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { FORMAT, VERSION } from './types.js';
|
|
2
|
+
export type { AnimationDef, AnimationFrameDef, FrameDef, LayerDef, LoopMode, Point, Rect, SpriteBuddyFile, Trim, } from './types.js';
|
|
3
|
+
export { DEFAULT_DURATION, FormatError, parseFile } from './parse.js';
|
|
4
|
+
export { cycleDuration, cycleOrder, eventsBetween, frameAt, type FrameEvent, type PlaybackPosition, } from './timing.js';
|
|
5
|
+
export { boundsOf, partsOf, type Part } from './resolve.js';
|
|
6
|
+
export { Player, type PlayerEvents, type PlayerState } from './player.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { FORMAT, VERSION } from './types.js';
|
|
2
|
+
export { DEFAULT_DURATION, FormatError, parseFile } from './parse.js';
|
|
3
|
+
export { cycleDuration, cycleOrder, eventsBetween, frameAt, } from './timing.js';
|
|
4
|
+
export { boundsOf, partsOf } from './resolve.js';
|
|
5
|
+
export { Player } from './player.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAY7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EACL,aAAa,EACb,UAAU,EACV,aAAa,EACb,OAAO,GAGR,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAa,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAuC,MAAM,aAAa,CAAC"}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type SpriteBuddyFile } from './types.js';
|
|
2
|
+
/** Thrown for a file that isn't a readable SpriteBuddy runtime file; `path` says where. */
|
|
3
|
+
export declare class FormatError extends Error {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
constructor(message: string, path: string);
|
|
6
|
+
}
|
|
7
|
+
/** Default display time of a frame when neither it nor its animation says, in ms. */
|
|
8
|
+
export declare const DEFAULT_DURATION = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Reads a SpriteBuddy runtime file (`.anims.json`, version 3) from parsed JSON, filling in every
|
|
11
|
+
* default. Unknown fields are ignored so newer minor additions still load. Throws
|
|
12
|
+
* {@link FormatError} saying where the file is wrong.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseFile(json: unknown): SpriteBuddyFile;
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { FORMAT, VERSION, } from './types.js';
|
|
2
|
+
/** Thrown for a file that isn't a readable SpriteBuddy runtime file; `path` says where. */
|
|
3
|
+
export class FormatError extends Error {
|
|
4
|
+
constructor(message, path) {
|
|
5
|
+
super(path ? `${path}: ${message}` : message);
|
|
6
|
+
this.path = path;
|
|
7
|
+
this.name = 'FormatError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Default display time of a frame when neither it nor its animation says, in ms. */
|
|
11
|
+
export const DEFAULT_DURATION = 100;
|
|
12
|
+
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
13
|
+
function object(v, path) {
|
|
14
|
+
if (!isObject(v))
|
|
15
|
+
throw new FormatError('expected an object', path);
|
|
16
|
+
return v;
|
|
17
|
+
}
|
|
18
|
+
function array(v, path) {
|
|
19
|
+
if (!Array.isArray(v))
|
|
20
|
+
throw new FormatError('expected a list', path);
|
|
21
|
+
return v;
|
|
22
|
+
}
|
|
23
|
+
function string(v, path) {
|
|
24
|
+
if (typeof v !== 'string' || v === '')
|
|
25
|
+
throw new FormatError('expected a non-empty string', path);
|
|
26
|
+
return v;
|
|
27
|
+
}
|
|
28
|
+
function int(v, path, min = -Infinity) {
|
|
29
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < min) {
|
|
30
|
+
throw new FormatError(min > -Infinity ? `expected a whole number ≥ ${min}` : 'expected a whole number', path);
|
|
31
|
+
}
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
function number(v, path, min, max) {
|
|
35
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v < min || v > max) {
|
|
36
|
+
throw new FormatError(`expected a number from ${min} to ${max}`, path);
|
|
37
|
+
}
|
|
38
|
+
return v;
|
|
39
|
+
}
|
|
40
|
+
function bool(v, path) {
|
|
41
|
+
if (typeof v !== 'boolean')
|
|
42
|
+
throw new FormatError('expected true or false', path);
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
45
|
+
/** A list of whole numbers of a fixed length, such as `[x, y]`. */
|
|
46
|
+
function ints(v, path, length, min) {
|
|
47
|
+
const list = array(v, path);
|
|
48
|
+
if (list.length !== length)
|
|
49
|
+
throw new FormatError(`expected ${length} numbers`, path);
|
|
50
|
+
return list.map((n, i) => int(n, `${path}[${i}]`, min[i]));
|
|
51
|
+
}
|
|
52
|
+
const opt = (v, fallback, read) => v === undefined ? fallback : read(v);
|
|
53
|
+
function frameDef(v, path, sheet) {
|
|
54
|
+
const o = object(v, path);
|
|
55
|
+
const [x, y, w, h] = ints(o.rect, `${path}.rect`, 4, [0, 0, 1, 1]);
|
|
56
|
+
const rotated = opt(o.rotated, false, (r) => bool(r, `${path}.rotated`));
|
|
57
|
+
const [cols, rows] = rotated ? [h, w] : [w, h];
|
|
58
|
+
if (x + cols > sheet.w || y + rows > sheet.h) {
|
|
59
|
+
throw new FormatError('lies outside the sheet', `${path}.rect`);
|
|
60
|
+
}
|
|
61
|
+
let trim = null;
|
|
62
|
+
let width = w;
|
|
63
|
+
let height = h;
|
|
64
|
+
if (o.trim !== undefined) {
|
|
65
|
+
const [tx, ty, fw, fh] = ints(o.trim, `${path}.trim`, 4, [0, 0, 1, 1]);
|
|
66
|
+
if (tx + w > fw || ty + h > fh) {
|
|
67
|
+
throw new FormatError('the trimmed pixels lie outside the full frame', `${path}.trim`);
|
|
68
|
+
}
|
|
69
|
+
trim = { x: tx, y: ty };
|
|
70
|
+
width = fw;
|
|
71
|
+
height = fh;
|
|
72
|
+
}
|
|
73
|
+
const pivot = array(o.pivot ?? [width / 2, height], `${path}.pivot`);
|
|
74
|
+
if (pivot.length !== 2)
|
|
75
|
+
throw new FormatError('expected 2 numbers', `${path}.pivot`);
|
|
76
|
+
const [px, py] = pivot.map((n, i) => number(n, `${path}.pivot[${i}]`, -1e6, 1e6));
|
|
77
|
+
return {
|
|
78
|
+
name: string(o.name, `${path}.name`),
|
|
79
|
+
rect: { x, y, w, h },
|
|
80
|
+
pivot: { x: px, y: py },
|
|
81
|
+
rotated,
|
|
82
|
+
trim,
|
|
83
|
+
width,
|
|
84
|
+
height,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function frameIndex(v, path, count) {
|
|
88
|
+
const i = int(v, path, 0);
|
|
89
|
+
if (i >= count)
|
|
90
|
+
throw new FormatError(`no frame ${i}; the file has ${count}`, path);
|
|
91
|
+
return i;
|
|
92
|
+
}
|
|
93
|
+
function layerDef(v, path, count) {
|
|
94
|
+
const o = object(v, path);
|
|
95
|
+
return {
|
|
96
|
+
frame: frameIndex(o.frame, `${path}.frame`, count),
|
|
97
|
+
x: opt(o.x, 0, (n) => int(n, `${path}.x`)),
|
|
98
|
+
y: opt(o.y, 0, (n) => int(n, `${path}.y`)),
|
|
99
|
+
z: opt(o.z, 1, (n) => int(n, `${path}.z`)),
|
|
100
|
+
opacity: opt(o.opacity, 1, (n) => number(n, `${path}.opacity`, 0, 1)),
|
|
101
|
+
flipX: opt(o.flipX, false, (b) => bool(b, `${path}.flipX`)),
|
|
102
|
+
flipY: opt(o.flipY, false, (b) => bool(b, `${path}.flipY`)),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function animationFrameDef(v, path, count, duration) {
|
|
106
|
+
if (typeof v === 'number') {
|
|
107
|
+
return {
|
|
108
|
+
frame: frameIndex(v, path, count),
|
|
109
|
+
duration,
|
|
110
|
+
offset: { x: 0, y: 0 },
|
|
111
|
+
layers: [],
|
|
112
|
+
events: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const o = object(v, path);
|
|
116
|
+
const [ox, oy] = opt(o.offset, [0, 0], (n) => ints(n, `${path}.offset`, 2, [-Infinity, -Infinity]));
|
|
117
|
+
return {
|
|
118
|
+
frame: frameIndex(o.frame, `${path}.frame`, count),
|
|
119
|
+
duration: opt(o.duration, duration, (n) => int(n, `${path}.duration`, 1)),
|
|
120
|
+
offset: { x: ox, y: oy },
|
|
121
|
+
layers: opt(o.layers, [], (l) => array(l, `${path}.layers`).map((layer, i) => layerDef(layer, `${path}.layers[${i}]`, count))),
|
|
122
|
+
events: opt(o.events, [], (e) => array(e, `${path}.events`).map((name, i) => string(name, `${path}.events[${i}]`))),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const LOOPS = ['loop', 'pingpong', 'once'];
|
|
126
|
+
function animationDef(v, path, count) {
|
|
127
|
+
const o = object(v, path);
|
|
128
|
+
const loop = opt(o.loop, 'loop', (l) => {
|
|
129
|
+
if (!LOOPS.includes(l)) {
|
|
130
|
+
throw new FormatError('expected "loop", "pingpong" or "once"', `${path}.loop`);
|
|
131
|
+
}
|
|
132
|
+
return l;
|
|
133
|
+
});
|
|
134
|
+
const duration = opt(o.duration, DEFAULT_DURATION, (n) => int(n, `${path}.duration`, 1));
|
|
135
|
+
return {
|
|
136
|
+
name: string(o.name, `${path}.name`),
|
|
137
|
+
loop,
|
|
138
|
+
frames: array(o.frames, `${path}.frames`).map((f, i) => animationFrameDef(f, `${path}.frames[${i}]`, count, duration)),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reads a SpriteBuddy runtime file (`.anims.json`, version 3) from parsed JSON, filling in every
|
|
143
|
+
* default. Unknown fields are ignored so newer minor additions still load. Throws
|
|
144
|
+
* {@link FormatError} saying where the file is wrong.
|
|
145
|
+
*/
|
|
146
|
+
export function parseFile(json) {
|
|
147
|
+
const o = object(json, '');
|
|
148
|
+
if (o.format !== FORMAT) {
|
|
149
|
+
throw new FormatError(`not a SpriteBuddy animations file (format should be "${FORMAT}")`, 'format');
|
|
150
|
+
}
|
|
151
|
+
const version = int(o.version, 'version', 1);
|
|
152
|
+
if (version < 3) {
|
|
153
|
+
throw new FormatError(`version ${version} files need the sheet's own JSON; export again from SpriteBuddy for version ${VERSION}`, 'version');
|
|
154
|
+
}
|
|
155
|
+
if (version > VERSION) {
|
|
156
|
+
throw new FormatError(`version ${version} is newer than this runtime reads (${VERSION}); update @spritebuddy packages`, 'version');
|
|
157
|
+
}
|
|
158
|
+
const [width, height] = ints(o.size, 'size', 2, [1, 1]);
|
|
159
|
+
const sheet = { w: width, h: height };
|
|
160
|
+
const frames = array(o.frames, 'frames').map((f, i) => frameDef(f, `frames[${i}]`, sheet));
|
|
161
|
+
const animations = opt(o.animations, [], (a) => array(a, 'animations').map((anim, i) => animationDef(anim, `animations[${i}]`, frames.length)));
|
|
162
|
+
const names = new Set();
|
|
163
|
+
animations.forEach((a, i) => {
|
|
164
|
+
if (names.has(a.name))
|
|
165
|
+
throw new FormatError(`duplicate animation "${a.name}"`, `animations[${i}].name`);
|
|
166
|
+
names.add(a.name);
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
version,
|
|
170
|
+
image: string(o.image, 'image'),
|
|
171
|
+
width,
|
|
172
|
+
height,
|
|
173
|
+
scale: opt(o.scale, 1, (s) => number(s, 'scale', 1e-6, 1e6)),
|
|
174
|
+
frames,
|
|
175
|
+
animations,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Part } from './resolve.js';
|
|
2
|
+
import type { AnimationDef, SpriteBuddyFile } from './types.js';
|
|
3
|
+
export interface PlayerState {
|
|
4
|
+
/** The animation playing, or null before `play`. */
|
|
5
|
+
readonly animation: AnimationDef | null;
|
|
6
|
+
/** Index into the animation's frames, or -1. */
|
|
7
|
+
readonly index: number;
|
|
8
|
+
/** Ms since the animation started, scaled by `speed`. */
|
|
9
|
+
readonly time: number;
|
|
10
|
+
readonly done: boolean;
|
|
11
|
+
/** What to draw now, bottom to top. */
|
|
12
|
+
readonly parts: readonly Part[];
|
|
13
|
+
}
|
|
14
|
+
export interface PlayerEvents {
|
|
15
|
+
/** Called with the frame index each time the frame changes. */
|
|
16
|
+
onFrame?: (index: number) => void;
|
|
17
|
+
/** Called when a `once` animation ends. */
|
|
18
|
+
onComplete?: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Called for each event of a frame as it starts showing (see {@link eventsBetween}). `play`
|
|
21
|
+
* fires the first frame's events; `seek` jumps without firing any.
|
|
22
|
+
*/
|
|
23
|
+
onEvent?: (name: string, index: number) => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Plays a file's animations with nothing but a clock: call {@link update} every tick with the
|
|
27
|
+
* elapsed ms and draw {@link parts}. Engine adapters wrap this; games can use it directly too.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Player {
|
|
30
|
+
#private;
|
|
31
|
+
readonly file: SpriteBuddyFile;
|
|
32
|
+
/** Playback rate: 1 is normal, 2 twice as fast, 0 frozen. */
|
|
33
|
+
speed: number;
|
|
34
|
+
/** Whether {@link update} moves time on. */
|
|
35
|
+
playing: boolean;
|
|
36
|
+
constructor(file: SpriteBuddyFile, events?: PlayerEvents);
|
|
37
|
+
/** Names of every animation in the file. */
|
|
38
|
+
get names(): string[];
|
|
39
|
+
/** The animation called `name`, or undefined. */
|
|
40
|
+
animation(name: string): AnimationDef | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Starts an animation from its first frame (or from `time` ms). Throws for an unknown name,
|
|
43
|
+
* listing the ones there are.
|
|
44
|
+
*/
|
|
45
|
+
play(name: string, time?: number): this;
|
|
46
|
+
stop(): this;
|
|
47
|
+
/** Jumps to `time` ms into the current animation. */
|
|
48
|
+
seek(time: number): this;
|
|
49
|
+
/** Moves time on by `ms` (times {@link speed}) while playing. */
|
|
50
|
+
update(ms: number): this;
|
|
51
|
+
get state(): PlayerState;
|
|
52
|
+
get parts(): readonly Part[];
|
|
53
|
+
}
|
package/dist/player.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _Player_instances, _Player_animation, _Player_time, _Player_index, _Player_done, _Player_parts, _Player_onFrame, _Player_onComplete, _Player_onEvent, _Player_refresh;
|
|
13
|
+
import { partsOf } from './resolve.js';
|
|
14
|
+
import { eventsBetween, frameAt } from './timing.js';
|
|
15
|
+
/**
|
|
16
|
+
* Plays a file's animations with nothing but a clock: call {@link update} every tick with the
|
|
17
|
+
* elapsed ms and draw {@link parts}. Engine adapters wrap this; games can use it directly too.
|
|
18
|
+
*/
|
|
19
|
+
export class Player {
|
|
20
|
+
constructor(file, events = {}) {
|
|
21
|
+
_Player_instances.add(this);
|
|
22
|
+
/** Playback rate: 1 is normal, 2 twice as fast, 0 frozen. */
|
|
23
|
+
this.speed = 1;
|
|
24
|
+
/** Whether {@link update} moves time on. */
|
|
25
|
+
this.playing = false;
|
|
26
|
+
_Player_animation.set(this, null);
|
|
27
|
+
_Player_time.set(this, 0);
|
|
28
|
+
_Player_index.set(this, -1);
|
|
29
|
+
_Player_done.set(this, false);
|
|
30
|
+
_Player_parts.set(this, []);
|
|
31
|
+
_Player_onFrame.set(this, void 0);
|
|
32
|
+
_Player_onComplete.set(this, void 0);
|
|
33
|
+
_Player_onEvent.set(this, void 0);
|
|
34
|
+
this.file = file;
|
|
35
|
+
__classPrivateFieldSet(this, _Player_onFrame, events.onFrame, "f");
|
|
36
|
+
__classPrivateFieldSet(this, _Player_onComplete, events.onComplete, "f");
|
|
37
|
+
__classPrivateFieldSet(this, _Player_onEvent, events.onEvent, "f");
|
|
38
|
+
}
|
|
39
|
+
/** Names of every animation in the file. */
|
|
40
|
+
get names() {
|
|
41
|
+
return this.file.animations.map((a) => a.name);
|
|
42
|
+
}
|
|
43
|
+
/** The animation called `name`, or undefined. */
|
|
44
|
+
animation(name) {
|
|
45
|
+
return this.file.animations.find((a) => a.name === name);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Starts an animation from its first frame (or from `time` ms). Throws for an unknown name,
|
|
49
|
+
* listing the ones there are.
|
|
50
|
+
*/
|
|
51
|
+
play(name, time = 0) {
|
|
52
|
+
const anim = this.animation(name);
|
|
53
|
+
if (!anim) {
|
|
54
|
+
throw new Error(`No animation "${name}"; this file has ${this.names.map((n) => `"${n}"`).join(', ') || 'none'}`);
|
|
55
|
+
}
|
|
56
|
+
__classPrivateFieldSet(this, _Player_animation, anim, "f");
|
|
57
|
+
__classPrivateFieldSet(this, _Player_index, -1, "f");
|
|
58
|
+
__classPrivateFieldSet(this, _Player_done, false, "f");
|
|
59
|
+
this.playing = true;
|
|
60
|
+
__classPrivateFieldSet(this, _Player_time, Math.max(0, time), "f");
|
|
61
|
+
const index = frameAt(anim, __classPrivateFieldGet(this, _Player_time, "f")).index;
|
|
62
|
+
__classPrivateFieldGet(this, _Player_instances, "m", _Player_refresh).call(this, index < 0 ? [] : anim.frames[index].events.map((name) => ({ name, index })));
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
stop() {
|
|
66
|
+
this.playing = false;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
/** Jumps to `time` ms into the current animation. */
|
|
70
|
+
seek(time) {
|
|
71
|
+
__classPrivateFieldSet(this, _Player_time, Math.max(0, time), "f");
|
|
72
|
+
__classPrivateFieldGet(this, _Player_instances, "m", _Player_refresh).call(this);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
/** Moves time on by `ms` (times {@link speed}) while playing. */
|
|
76
|
+
update(ms) {
|
|
77
|
+
const anim = __classPrivateFieldGet(this, _Player_animation, "f");
|
|
78
|
+
if (this.playing && anim) {
|
|
79
|
+
const from = __classPrivateFieldGet(this, _Player_time, "f");
|
|
80
|
+
__classPrivateFieldSet(this, _Player_time, __classPrivateFieldGet(this, _Player_time, "f") + Math.max(0, ms) * this.speed, "f");
|
|
81
|
+
__classPrivateFieldGet(this, _Player_instances, "m", _Player_refresh).call(this, __classPrivateFieldGet(this, _Player_onEvent, "f") ? eventsBetween(anim, from, __classPrivateFieldGet(this, _Player_time, "f")) : []);
|
|
82
|
+
}
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
get state() {
|
|
86
|
+
return {
|
|
87
|
+
animation: __classPrivateFieldGet(this, _Player_animation, "f"),
|
|
88
|
+
index: __classPrivateFieldGet(this, _Player_index, "f"),
|
|
89
|
+
time: __classPrivateFieldGet(this, _Player_time, "f"),
|
|
90
|
+
done: __classPrivateFieldGet(this, _Player_done, "f"),
|
|
91
|
+
parts: __classPrivateFieldGet(this, _Player_parts, "f"),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
get parts() {
|
|
95
|
+
return __classPrivateFieldGet(this, _Player_parts, "f");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
_Player_animation = new WeakMap(), _Player_time = new WeakMap(), _Player_index = new WeakMap(), _Player_done = new WeakMap(), _Player_parts = new WeakMap(), _Player_onFrame = new WeakMap(), _Player_onComplete = new WeakMap(), _Player_onEvent = new WeakMap(), _Player_instances = new WeakSet(), _Player_refresh = function _Player_refresh(fired = []) {
|
|
99
|
+
const anim = __classPrivateFieldGet(this, _Player_animation, "f");
|
|
100
|
+
if (!anim)
|
|
101
|
+
return;
|
|
102
|
+
const pos = frameAt(anim, __classPrivateFieldGet(this, _Player_time, "f"));
|
|
103
|
+
const changed = pos.index !== __classPrivateFieldGet(this, _Player_index, "f");
|
|
104
|
+
__classPrivateFieldSet(this, _Player_index, pos.index, "f");
|
|
105
|
+
__classPrivateFieldSet(this, _Player_parts, pos.index < 0 ? [] : partsOf(this.file, anim.frames[pos.index]), "f");
|
|
106
|
+
if (changed && pos.index >= 0)
|
|
107
|
+
__classPrivateFieldGet(this, _Player_onFrame, "f")?.call(this, pos.index);
|
|
108
|
+
for (const e of fired)
|
|
109
|
+
__classPrivateFieldGet(this, _Player_onEvent, "f")?.call(this, e.name, e.index);
|
|
110
|
+
if (pos.done && !__classPrivateFieldGet(this, _Player_done, "f")) {
|
|
111
|
+
__classPrivateFieldSet(this, _Player_done, true, "f");
|
|
112
|
+
this.playing = false;
|
|
113
|
+
__classPrivateFieldGet(this, _Player_onComplete, "f")?.call(this);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AnimationFrameDef, FrameDef, SpriteBuddyFile } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* One image to draw for an animation frame: the base frame or one of its layers, in draw order.
|
|
4
|
+
* `x, y` is where the part's full (untrimmed, upright) frame has its top-left, relative to the
|
|
5
|
+
* sprite's origin: the base frame's pivot, already moved by the frame's offset. So a renderer
|
|
6
|
+
* puts the sprite where the character stands and draws each part at `origin + (x, y)`.
|
|
7
|
+
*/
|
|
8
|
+
export interface Part {
|
|
9
|
+
readonly frame: FrameDef;
|
|
10
|
+
/** Index into the file's frames. */
|
|
11
|
+
readonly index: number;
|
|
12
|
+
readonly x: number;
|
|
13
|
+
readonly y: number;
|
|
14
|
+
readonly opacity: number;
|
|
15
|
+
/** Mirrored within the part's own full frame. */
|
|
16
|
+
readonly flipX: boolean;
|
|
17
|
+
readonly flipY: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Everything to draw for one animation frame, bottom to top: the base frame (z 0) and its
|
|
21
|
+
* layers sorted by z, ties keeping the base first and then list order.
|
|
22
|
+
*/
|
|
23
|
+
export declare function partsOf(file: Pick<SpriteBuddyFile, 'frames'>, entry: AnimationFrameDef): Part[];
|
|
24
|
+
/** Box around every part of an animation frame, relative to the sprite's origin. */
|
|
25
|
+
export declare function boundsOf(parts: readonly Part[]): {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
w: number;
|
|
29
|
+
h: number;
|
|
30
|
+
};
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything to draw for one animation frame, bottom to top: the base frame (z 0) and its
|
|
3
|
+
* layers sorted by z, ties keeping the base first and then list order.
|
|
4
|
+
*/
|
|
5
|
+
export function partsOf(file, entry) {
|
|
6
|
+
const base = file.frames[entry.frame];
|
|
7
|
+
const left = -base.pivot.x + entry.offset.x;
|
|
8
|
+
const top = -base.pivot.y + entry.offset.y;
|
|
9
|
+
const stack = [
|
|
10
|
+
{
|
|
11
|
+
z: 0,
|
|
12
|
+
part: {
|
|
13
|
+
frame: base,
|
|
14
|
+
index: entry.frame,
|
|
15
|
+
x: left,
|
|
16
|
+
y: top,
|
|
17
|
+
opacity: 1,
|
|
18
|
+
flipX: false,
|
|
19
|
+
flipY: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
...entry.layers.map((l) => ({
|
|
23
|
+
z: l.z,
|
|
24
|
+
part: {
|
|
25
|
+
frame: file.frames[l.frame],
|
|
26
|
+
index: l.frame,
|
|
27
|
+
x: left + l.x,
|
|
28
|
+
y: top + l.y,
|
|
29
|
+
opacity: l.opacity,
|
|
30
|
+
flipX: l.flipX,
|
|
31
|
+
flipY: l.flipY,
|
|
32
|
+
},
|
|
33
|
+
})),
|
|
34
|
+
];
|
|
35
|
+
return stack
|
|
36
|
+
.map((s, order) => ({ ...s, order }))
|
|
37
|
+
.sort((a, b) => a.z - b.z || a.order - b.order)
|
|
38
|
+
.map((s) => s.part);
|
|
39
|
+
}
|
|
40
|
+
/** Box around every part of an animation frame, relative to the sprite's origin. */
|
|
41
|
+
export function boundsOf(parts) {
|
|
42
|
+
if (parts.length === 0)
|
|
43
|
+
return { x: 0, y: 0, w: 0, h: 0 };
|
|
44
|
+
const x0 = Math.min(...parts.map((p) => p.x));
|
|
45
|
+
const y0 = Math.min(...parts.map((p) => p.y));
|
|
46
|
+
const x1 = Math.max(...parts.map((p) => p.x + p.frame.width));
|
|
47
|
+
const y1 = Math.max(...parts.map((p) => p.y + p.frame.height));
|
|
48
|
+
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
|
package/dist/timing.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AnimationDef } from './types.js';
|
|
2
|
+
export interface PlaybackPosition {
|
|
3
|
+
/** Index into the animation's `frames`, or -1 for an animation with no frames. */
|
|
4
|
+
readonly index: number;
|
|
5
|
+
/** True once a `once` animation has reached its end (it then holds the last frame). */
|
|
6
|
+
readonly done: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The frame indices one cycle visits. `pingpong` plays forward then back without repeating the
|
|
10
|
+
* end frames (0 1 2 3 2 1), so with two frames or fewer it is the same as `loop`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function cycleOrder(anim: Pick<AnimationDef, 'frames' | 'loop'>): number[];
|
|
13
|
+
/** Length of one cycle in ms (forward and back for `pingpong`). */
|
|
14
|
+
export declare function cycleDuration(anim: Pick<AnimationDef, 'frames' | 'loop'>): number;
|
|
15
|
+
/**
|
|
16
|
+
* Which frame shows `time` ms after the animation starts. A frame shows from its start time up
|
|
17
|
+
* to, not including, its end; `loop` and `pingpong` repeat forever, `once` holds the last frame.
|
|
18
|
+
* Negative times count as 0.
|
|
19
|
+
*/
|
|
20
|
+
export declare function frameAt(anim: Pick<AnimationDef, 'frames' | 'loop'>, time: number): PlaybackPosition;
|
|
21
|
+
/** An event fired by an animation frame: its name and the frame's index in the animation. */
|
|
22
|
+
export interface FrameEvent {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly index: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The events of every frame that starts showing after `from` ms and up to and including `to` ms,
|
|
28
|
+
* in the order they start. A frame that starts exactly at `from` was already counted, so calling
|
|
29
|
+
* this with each tick's old and new time fires every event once, even when a long tick skips
|
|
30
|
+
* past short frames. A ping-pong cycle fires a frame's events each time it shows. One call fires
|
|
31
|
+
* at most one cycle's worth, so a game that was paused for a minute doesn't get a burst.
|
|
32
|
+
*/
|
|
33
|
+
export declare function eventsBetween(anim: Pick<AnimationDef, 'frames' | 'loop'>, from: number, to: number): FrameEvent[];
|
package/dist/timing.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frame indices one cycle visits. `pingpong` plays forward then back without repeating the
|
|
3
|
+
* end frames (0 1 2 3 2 1), so with two frames or fewer it is the same as `loop`.
|
|
4
|
+
*/
|
|
5
|
+
export function cycleOrder(anim) {
|
|
6
|
+
const forward = anim.frames.map((_, i) => i);
|
|
7
|
+
if (anim.loop !== 'pingpong' || forward.length <= 2)
|
|
8
|
+
return forward;
|
|
9
|
+
return [...forward, ...forward.slice(1, -1).reverse()];
|
|
10
|
+
}
|
|
11
|
+
/** Length of one cycle in ms (forward and back for `pingpong`). */
|
|
12
|
+
export function cycleDuration(anim) {
|
|
13
|
+
return cycleOrder(anim).reduce((t, i) => t + anim.frames[i].duration, 0);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Which frame shows `time` ms after the animation starts. A frame shows from its start time up
|
|
17
|
+
* to, not including, its end; `loop` and `pingpong` repeat forever, `once` holds the last frame.
|
|
18
|
+
* Negative times count as 0.
|
|
19
|
+
*/
|
|
20
|
+
export function frameAt(anim, time) {
|
|
21
|
+
const order = cycleOrder(anim);
|
|
22
|
+
if (order.length === 0)
|
|
23
|
+
return { index: -1, done: true };
|
|
24
|
+
const total = cycleDuration(anim);
|
|
25
|
+
const t = Math.max(0, time);
|
|
26
|
+
if (anim.loop === 'once' && t >= total)
|
|
27
|
+
return { index: order[order.length - 1], done: true };
|
|
28
|
+
let rest = t % total;
|
|
29
|
+
for (const i of order) {
|
|
30
|
+
rest -= anim.frames[i].duration;
|
|
31
|
+
if (rest < 0)
|
|
32
|
+
return { index: i, done: false };
|
|
33
|
+
}
|
|
34
|
+
/* v8 ignore next */
|
|
35
|
+
return { index: order[order.length - 1], done: false };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The events of every frame that starts showing after `from` ms and up to and including `to` ms,
|
|
39
|
+
* in the order they start. A frame that starts exactly at `from` was already counted, so calling
|
|
40
|
+
* this with each tick's old and new time fires every event once, even when a long tick skips
|
|
41
|
+
* past short frames. A ping-pong cycle fires a frame's events each time it shows. One call fires
|
|
42
|
+
* at most one cycle's worth, so a game that was paused for a minute doesn't get a burst.
|
|
43
|
+
*/
|
|
44
|
+
export function eventsBetween(anim, from, to) {
|
|
45
|
+
const order = cycleOrder(anim);
|
|
46
|
+
if (order.length === 0 || to <= from)
|
|
47
|
+
return [];
|
|
48
|
+
if (!anim.frames.some((f) => f.events.length > 0))
|
|
49
|
+
return [];
|
|
50
|
+
const total = cycleDuration(anim);
|
|
51
|
+
const starts = [];
|
|
52
|
+
let t = 0;
|
|
53
|
+
for (const i of order) {
|
|
54
|
+
starts.push(t);
|
|
55
|
+
t += anim.frames[i].duration;
|
|
56
|
+
}
|
|
57
|
+
const crossed = [];
|
|
58
|
+
const once = anim.loop === 'once';
|
|
59
|
+
const first = once
|
|
60
|
+
? 0
|
|
61
|
+
: Math.max(Math.floor(Math.max(0, from) / total), Math.floor(to / total) - 1);
|
|
62
|
+
const last = once ? 0 : Math.floor(to / total);
|
|
63
|
+
for (let cycle = first; cycle <= last; cycle++) {
|
|
64
|
+
order.forEach((index, k) => {
|
|
65
|
+
const at = cycle * total + starts[k];
|
|
66
|
+
if (at > from && at <= to)
|
|
67
|
+
crossed.push(index);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return crossed
|
|
71
|
+
.slice(-order.length)
|
|
72
|
+
.flatMap((index) => anim.frames[index].events.map((name) => ({ name, index })));
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** `format` of every SpriteBuddy runtime file. */
|
|
2
|
+
export declare const FORMAT = "spritebuddy-animations";
|
|
3
|
+
/** The newest runtime file version this package reads. */
|
|
4
|
+
export declare const VERSION = 3;
|
|
5
|
+
export type LoopMode = 'loop' | 'pingpong' | 'once';
|
|
6
|
+
/**
|
|
7
|
+
* One frame on the sheet. Coordinates are pixels with y down, as in the image.
|
|
8
|
+
*
|
|
9
|
+
* - `rect` is where the frame's pixels sit on the sheet, at their upright size. A `rotated`
|
|
10
|
+
* frame is stored turned 90° clockwise, so it covers `rect.h` columns and `rect.w` rows
|
|
11
|
+
* starting at `rect.x, rect.y` (TexturePacker's rule).
|
|
12
|
+
* - `trim` is set when transparent edges were cut off: the cut pixels' place inside the full
|
|
13
|
+
* frame and the full frame's size. Without it the full frame is `rect`.
|
|
14
|
+
* - `pivot` is in full-frame pixels; the frame hangs from it (feet, usually).
|
|
15
|
+
*/
|
|
16
|
+
export interface FrameDef {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly rect: Rect;
|
|
19
|
+
readonly pivot: Point;
|
|
20
|
+
readonly rotated: boolean;
|
|
21
|
+
readonly trim: Trim | null;
|
|
22
|
+
/** Full (untrimmed) frame size. */
|
|
23
|
+
readonly width: number;
|
|
24
|
+
readonly height: number;
|
|
25
|
+
}
|
|
26
|
+
export interface Rect {
|
|
27
|
+
readonly x: number;
|
|
28
|
+
readonly y: number;
|
|
29
|
+
readonly w: number;
|
|
30
|
+
readonly h: number;
|
|
31
|
+
}
|
|
32
|
+
export interface Point {
|
|
33
|
+
readonly x: number;
|
|
34
|
+
readonly y: number;
|
|
35
|
+
}
|
|
36
|
+
export interface Trim {
|
|
37
|
+
/** Where the trimmed pixels start inside the full frame. */
|
|
38
|
+
readonly x: number;
|
|
39
|
+
readonly y: number;
|
|
40
|
+
}
|
|
41
|
+
/** An extra frame drawn with an animation frame: a weapon, an effect, a shadow. */
|
|
42
|
+
export interface LayerDef {
|
|
43
|
+
/** Index into {@link SpriteBuddyFile.frames}. */
|
|
44
|
+
readonly frame: number;
|
|
45
|
+
/** Top-left of the layer's full frame, relative to the base frame's full top-left. */
|
|
46
|
+
readonly x: number;
|
|
47
|
+
readonly y: number;
|
|
48
|
+
/** Draw order: the base frame is 0, higher draws on top, ties keep list order. */
|
|
49
|
+
readonly z: number;
|
|
50
|
+
readonly opacity: number;
|
|
51
|
+
readonly flipX: boolean;
|
|
52
|
+
readonly flipY: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface AnimationFrameDef {
|
|
55
|
+
/** Index into {@link SpriteBuddyFile.frames}. */
|
|
56
|
+
readonly frame: number;
|
|
57
|
+
/** Display time in ms. */
|
|
58
|
+
readonly duration: number;
|
|
59
|
+
/** Nudge in pixels from where the pivot puts the frame (+x right, +y down). */
|
|
60
|
+
readonly offset: Point;
|
|
61
|
+
readonly layers: readonly LayerDef[];
|
|
62
|
+
/**
|
|
63
|
+
* Names fired when this frame starts showing, such as `"hit"` on the frame a sword lands or
|
|
64
|
+
* `"step"` on a footstep. Empty for most frames.
|
|
65
|
+
*/
|
|
66
|
+
readonly events: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
export interface AnimationDef {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly loop: LoopMode;
|
|
71
|
+
readonly frames: readonly AnimationFrameDef[];
|
|
72
|
+
}
|
|
73
|
+
/** A parsed runtime file, with every default filled in. */
|
|
74
|
+
export interface SpriteBuddyFile {
|
|
75
|
+
readonly version: number;
|
|
76
|
+
/** The sheet image's file name, relative to the runtime file. */
|
|
77
|
+
readonly image: string;
|
|
78
|
+
readonly width: number;
|
|
79
|
+
readonly height: number;
|
|
80
|
+
/** Scale the sheet was exported at (1 = original pixels). */
|
|
81
|
+
readonly scale: number;
|
|
82
|
+
readonly frames: readonly FrameDef[];
|
|
83
|
+
readonly animations: readonly AnimationDef[];
|
|
84
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,CAAC,MAAM,MAAM,GAAG,wBAAwB,CAAC;AAC/C,0DAA0D;AAC1D,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spritebuddy/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Reads SpriteBuddy animation files and works out what to draw at any moment. No dependencies, any engine.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "Patrick Hammond",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Patrick-Hammond/spritebuddy-runtimes.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"spritebuddy",
|
|
14
|
+
"sprites",
|
|
15
|
+
"spritesheet",
|
|
16
|
+
"animation",
|
|
17
|
+
"gamedev"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./schema.json": "./schema.json"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"schema.json"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.build.json && node -e \"require('node:fs').copyFileSync('../../schema/spritebuddy-animations.v3.schema.json', 'schema.json')\"",
|
|
37
|
+
"prepack": "npm run build",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/schema.json
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/Patrick-Hammond/spritebuddy-runtimes/blob/main/schema/spritebuddy-animations.v3.schema.json",
|
|
4
|
+
"title": "SpriteBuddy animations, version 3",
|
|
5
|
+
"description": "A spritesheet's frames and animations, with per-frame timing, offsets and layers. Coordinates are sheet pixels with y down; times are milliseconds. See docs/format.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["format", "version", "image", "size", "frames"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"format": { "const": "spritebuddy-animations" },
|
|
10
|
+
"version": { "const": 3 },
|
|
11
|
+
"image": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"minLength": 1,
|
|
14
|
+
"description": "The sheet image, relative to this file."
|
|
15
|
+
},
|
|
16
|
+
"size": {
|
|
17
|
+
"$ref": "#/$defs/size",
|
|
18
|
+
"description": "Sheet size in pixels: [width, height]."
|
|
19
|
+
},
|
|
20
|
+
"scale": {
|
|
21
|
+
"type": "number",
|
|
22
|
+
"exclusiveMinimum": 0,
|
|
23
|
+
"default": 1,
|
|
24
|
+
"description": "Scale the sheet was exported at. Informational: every coordinate in the file is already in sheet pixels."
|
|
25
|
+
},
|
|
26
|
+
"frames": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": { "$ref": "#/$defs/frame" }
|
|
29
|
+
},
|
|
30
|
+
"animations": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"default": [],
|
|
33
|
+
"items": { "$ref": "#/$defs/animation" }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"$defs": {
|
|
37
|
+
"int": { "type": "integer" },
|
|
38
|
+
"count": { "type": "integer", "minimum": 0 },
|
|
39
|
+
"positive": { "type": "integer", "minimum": 1 },
|
|
40
|
+
"size": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"prefixItems": [{ "$ref": "#/$defs/positive" }, { "$ref": "#/$defs/positive" }],
|
|
43
|
+
"minItems": 2,
|
|
44
|
+
"maxItems": 2
|
|
45
|
+
},
|
|
46
|
+
"point": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"prefixItems": [{ "type": "number" }, { "type": "number" }],
|
|
49
|
+
"minItems": 2,
|
|
50
|
+
"maxItems": 2
|
|
51
|
+
},
|
|
52
|
+
"offset": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"prefixItems": [{ "$ref": "#/$defs/int" }, { "$ref": "#/$defs/int" }],
|
|
55
|
+
"minItems": 2,
|
|
56
|
+
"maxItems": 2
|
|
57
|
+
},
|
|
58
|
+
"frame": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["name", "rect"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"name": { "type": "string", "minLength": 1 },
|
|
63
|
+
"rect": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"description": "[x, y, w, h]: where the pixels sit on the sheet, at their upright size.",
|
|
66
|
+
"prefixItems": [
|
|
67
|
+
{ "$ref": "#/$defs/count" },
|
|
68
|
+
{ "$ref": "#/$defs/count" },
|
|
69
|
+
{ "$ref": "#/$defs/positive" },
|
|
70
|
+
{ "$ref": "#/$defs/positive" }
|
|
71
|
+
],
|
|
72
|
+
"minItems": 4,
|
|
73
|
+
"maxItems": 4
|
|
74
|
+
},
|
|
75
|
+
"pivot": {
|
|
76
|
+
"$ref": "#/$defs/point",
|
|
77
|
+
"description": "[x, y] in full-frame pixels. Defaults to the bottom centre."
|
|
78
|
+
},
|
|
79
|
+
"rotated": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"default": false,
|
|
82
|
+
"description": "Stored turned 90° clockwise: covers h columns and w rows on the sheet."
|
|
83
|
+
},
|
|
84
|
+
"trim": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"description": "[x, y, fullWidth, fullHeight]: transparent edges were cut off; the pixels start at x, y inside a full frame of this size.",
|
|
87
|
+
"prefixItems": [
|
|
88
|
+
{ "$ref": "#/$defs/count" },
|
|
89
|
+
{ "$ref": "#/$defs/count" },
|
|
90
|
+
{ "$ref": "#/$defs/positive" },
|
|
91
|
+
{ "$ref": "#/$defs/positive" }
|
|
92
|
+
],
|
|
93
|
+
"minItems": 4,
|
|
94
|
+
"maxItems": 4
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"layer": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"required": ["frame"],
|
|
101
|
+
"properties": {
|
|
102
|
+
"frame": { "$ref": "#/$defs/count", "description": "Index into frames." },
|
|
103
|
+
"x": {
|
|
104
|
+
"$ref": "#/$defs/int",
|
|
105
|
+
"default": 0,
|
|
106
|
+
"description": "Layer's full-frame left, from the base frame's left."
|
|
107
|
+
},
|
|
108
|
+
"y": {
|
|
109
|
+
"$ref": "#/$defs/int",
|
|
110
|
+
"default": 0,
|
|
111
|
+
"description": "Layer's full-frame top, from the base frame's top."
|
|
112
|
+
},
|
|
113
|
+
"z": {
|
|
114
|
+
"$ref": "#/$defs/int",
|
|
115
|
+
"default": 1,
|
|
116
|
+
"description": "Draw order; the base frame is 0."
|
|
117
|
+
},
|
|
118
|
+
"opacity": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 },
|
|
119
|
+
"flipX": { "type": "boolean", "default": false },
|
|
120
|
+
"flipY": { "type": "boolean", "default": false }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"animationFrame": {
|
|
124
|
+
"oneOf": [
|
|
125
|
+
{ "$ref": "#/$defs/count", "description": "A frame index, with the animation's duration." },
|
|
126
|
+
{
|
|
127
|
+
"type": "object",
|
|
128
|
+
"required": ["frame"],
|
|
129
|
+
"properties": {
|
|
130
|
+
"frame": { "$ref": "#/$defs/count" },
|
|
131
|
+
"duration": { "$ref": "#/$defs/positive", "description": "Milliseconds." },
|
|
132
|
+
"offset": {
|
|
133
|
+
"$ref": "#/$defs/offset",
|
|
134
|
+
"description": "[x, y] nudge from where the pivot puts the frame; +x right, +y down."
|
|
135
|
+
},
|
|
136
|
+
"layers": { "type": "array", "items": { "$ref": "#/$defs/layer" } },
|
|
137
|
+
"events": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"description": "Names fired when this frame starts showing, such as \"hit\" or \"step\".",
|
|
140
|
+
"items": { "type": "string", "minLength": 1 }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"animation": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"required": ["name", "frames"],
|
|
149
|
+
"properties": {
|
|
150
|
+
"name": { "type": "string", "minLength": 1 },
|
|
151
|
+
"loop": { "enum": ["loop", "pingpong", "once"], "default": "loop" },
|
|
152
|
+
"duration": {
|
|
153
|
+
"$ref": "#/$defs/positive",
|
|
154
|
+
"default": 100,
|
|
155
|
+
"description": "Milliseconds for frames that don't give their own."
|
|
156
|
+
},
|
|
157
|
+
"frames": { "type": "array", "items": { "$ref": "#/$defs/animationFrame" } }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|