@al8b/sprites 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/README.md +23 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +145 -0
- package/dist/index.mjs.map +1 -0
- package/dist/sprite.d.mts +51 -0
- package/dist/sprite.d.ts +51 -0
- package/dist/sprite.js +170 -0
- package/dist/sprite.js.map +1 -0
- package/dist/sprite.mjs +145 -0
- package/dist/sprite.mjs.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @al8b/sprites
|
|
2
|
+
|
|
3
|
+
Sprite asset helpers for L8B. This package wraps image frames into runtime-friendly sprite objects and includes convenience loaders for updating sprite data from asset payloads.
|
|
4
|
+
|
|
5
|
+
## Public API
|
|
6
|
+
|
|
7
|
+
- `Sprite`
|
|
8
|
+
- `LoadSprite`
|
|
9
|
+
- `UpdateSprite`
|
|
10
|
+
- Type: `SpriteProperties`
|
|
11
|
+
|
|
12
|
+
## Notes
|
|
13
|
+
|
|
14
|
+
- Built on top of `@al8b/image`.
|
|
15
|
+
- Used by runtime asset loading and by packages that render sprite-backed content.
|
|
16
|
+
|
|
17
|
+
## Scripts
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun run build
|
|
21
|
+
bun run test
|
|
22
|
+
bun run clean
|
|
23
|
+
```
|
package/dist/index.d.mts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
LoadSprite: () => LoadSprite,
|
|
25
|
+
Sprite: () => Sprite,
|
|
26
|
+
UpdateSprite: () => UpdateSprite
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/sprite.ts
|
|
31
|
+
var import_image = require("@al8b/image");
|
|
32
|
+
var Sprite = class {
|
|
33
|
+
static {
|
|
34
|
+
__name(this, "Sprite");
|
|
35
|
+
}
|
|
36
|
+
name = "";
|
|
37
|
+
frames = [];
|
|
38
|
+
animation_start = 0;
|
|
39
|
+
fps = 5;
|
|
40
|
+
ready = 0;
|
|
41
|
+
width;
|
|
42
|
+
height;
|
|
43
|
+
constructor(width, height) {
|
|
44
|
+
this.width = width;
|
|
45
|
+
this.height = height;
|
|
46
|
+
this.frames = [];
|
|
47
|
+
this.animation_start = 0;
|
|
48
|
+
this.fps = 5;
|
|
49
|
+
if (this.width > 0 && this.height > 0) {
|
|
50
|
+
this.frames.push(new import_image.Image(this.width, this.height));
|
|
51
|
+
this.ready = 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set animation frames per second
|
|
56
|
+
* Preserves current animation position when changing FPS
|
|
57
|
+
*/
|
|
58
|
+
setFPS(fps) {
|
|
59
|
+
const dt = 1e3 / this.fps;
|
|
60
|
+
const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
|
|
61
|
+
this.fps = fps;
|
|
62
|
+
const newDt = 1e3 / fps;
|
|
63
|
+
this.animation_start = Date.now() - frame * newDt;
|
|
64
|
+
return fps;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Set the current animation frame
|
|
68
|
+
*/
|
|
69
|
+
setFrame(frame) {
|
|
70
|
+
this.animation_start = Date.now() - 1e3 / this.fps * frame;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the current animation frame index
|
|
74
|
+
*/
|
|
75
|
+
getFrame() {
|
|
76
|
+
const dt = 1e3 / this.fps;
|
|
77
|
+
return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the canvas of the current frame
|
|
81
|
+
*/
|
|
82
|
+
getCurrentFrameCanvas() {
|
|
83
|
+
if (!this.ready || this.frames.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (this.frames.length > 1) {
|
|
87
|
+
if (this.animation_start === 0) {
|
|
88
|
+
this.animation_start = Date.now();
|
|
89
|
+
}
|
|
90
|
+
const frame = this.getFrame();
|
|
91
|
+
if (frame >= 0 && frame < this.frames.length) {
|
|
92
|
+
return this.frames[frame].canvas;
|
|
93
|
+
}
|
|
94
|
+
return this.frames[0].canvas;
|
|
95
|
+
}
|
|
96
|
+
return this.frames[0].canvas;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function resolveFrameCount(imgWidth, imgHeight, requested) {
|
|
100
|
+
if (imgWidth > 0 && imgHeight % imgWidth === 0) {
|
|
101
|
+
const available = imgHeight / imgWidth;
|
|
102
|
+
return Math.min(requested, available);
|
|
103
|
+
}
|
|
104
|
+
return requested;
|
|
105
|
+
}
|
|
106
|
+
__name(resolveFrameCount, "resolveFrameCount");
|
|
107
|
+
function LoadSprite(url, properties, loaded) {
|
|
108
|
+
const sprite = new Sprite(0, 0);
|
|
109
|
+
sprite.ready = 0;
|
|
110
|
+
const img = new window.Image();
|
|
111
|
+
if (location.protocol !== "file:") {
|
|
112
|
+
img.crossOrigin = "Anonymous";
|
|
113
|
+
}
|
|
114
|
+
img.src = url;
|
|
115
|
+
img.onload = () => {
|
|
116
|
+
sprite.ready = 1;
|
|
117
|
+
if (img.width > 0 && img.height > 0) {
|
|
118
|
+
if (properties?.fps) {
|
|
119
|
+
sprite.fps = properties.fps;
|
|
120
|
+
}
|
|
121
|
+
sprite.width = img.width;
|
|
122
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
123
|
+
sprite.height = Math.round(img.height / numframes);
|
|
124
|
+
sprite.frames = [];
|
|
125
|
+
for (let i = 0; i < numframes; i++) {
|
|
126
|
+
const frame = new import_image.Image(sprite.width, sprite.height);
|
|
127
|
+
frame.initContext();
|
|
128
|
+
const ctx = frame.context;
|
|
129
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
130
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
131
|
+
sprite.frames.push(frame);
|
|
132
|
+
}
|
|
133
|
+
sprite.ready = 1;
|
|
134
|
+
}
|
|
135
|
+
if (loaded) {
|
|
136
|
+
loaded();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
img.onerror = () => {
|
|
140
|
+
sprite.ready = 1;
|
|
141
|
+
};
|
|
142
|
+
return sprite;
|
|
143
|
+
}
|
|
144
|
+
__name(LoadSprite, "LoadSprite");
|
|
145
|
+
function UpdateSprite(sprite, img, properties) {
|
|
146
|
+
if (img.width > 0 && img.height > 0) {
|
|
147
|
+
if (properties?.fps) {
|
|
148
|
+
sprite.fps = properties.fps;
|
|
149
|
+
}
|
|
150
|
+
sprite.width = img.width;
|
|
151
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
152
|
+
sprite.height = Math.round(img.height / numframes);
|
|
153
|
+
sprite.frames = [];
|
|
154
|
+
for (let i = 0; i < numframes; i++) {
|
|
155
|
+
const frame = new import_image.Image(sprite.width, sprite.height);
|
|
156
|
+
frame.initContext();
|
|
157
|
+
const ctx = frame.context;
|
|
158
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
159
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
160
|
+
sprite.frames.push(frame);
|
|
161
|
+
}
|
|
162
|
+
sprite.ready = 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
__name(UpdateSprite, "UpdateSprite");
|
|
166
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
167
|
+
0 && (module.exports = {
|
|
168
|
+
LoadSprite,
|
|
169
|
+
Sprite,
|
|
170
|
+
UpdateSprite
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sprite.ts"],"sourcesContent":["/**\n * @al8b/sprites - Sprite class for animated graphics\n *\n * Provides utilities for creating and manipulating sprites:\n * - Sprite class for animated sprite support\n * - Multi-frame animation support\n */\n\nexport { Sprite, LoadSprite, UpdateSprite } from \"./sprite\";\nexport type { SpriteProperties } from \"./sprite\";\n","/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/sprite.ts
|
|
5
|
+
import { Image } from "@al8b/image";
|
|
6
|
+
var Sprite = class {
|
|
7
|
+
static {
|
|
8
|
+
__name(this, "Sprite");
|
|
9
|
+
}
|
|
10
|
+
name = "";
|
|
11
|
+
frames = [];
|
|
12
|
+
animation_start = 0;
|
|
13
|
+
fps = 5;
|
|
14
|
+
ready = 0;
|
|
15
|
+
width;
|
|
16
|
+
height;
|
|
17
|
+
constructor(width, height) {
|
|
18
|
+
this.width = width;
|
|
19
|
+
this.height = height;
|
|
20
|
+
this.frames = [];
|
|
21
|
+
this.animation_start = 0;
|
|
22
|
+
this.fps = 5;
|
|
23
|
+
if (this.width > 0 && this.height > 0) {
|
|
24
|
+
this.frames.push(new Image(this.width, this.height));
|
|
25
|
+
this.ready = 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Set animation frames per second
|
|
30
|
+
* Preserves current animation position when changing FPS
|
|
31
|
+
*/
|
|
32
|
+
setFPS(fps) {
|
|
33
|
+
const dt = 1e3 / this.fps;
|
|
34
|
+
const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
|
|
35
|
+
this.fps = fps;
|
|
36
|
+
const newDt = 1e3 / fps;
|
|
37
|
+
this.animation_start = Date.now() - frame * newDt;
|
|
38
|
+
return fps;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Set the current animation frame
|
|
42
|
+
*/
|
|
43
|
+
setFrame(frame) {
|
|
44
|
+
this.animation_start = Date.now() - 1e3 / this.fps * frame;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the current animation frame index
|
|
48
|
+
*/
|
|
49
|
+
getFrame() {
|
|
50
|
+
const dt = 1e3 / this.fps;
|
|
51
|
+
return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the canvas of the current frame
|
|
55
|
+
*/
|
|
56
|
+
getCurrentFrameCanvas() {
|
|
57
|
+
if (!this.ready || this.frames.length === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (this.frames.length > 1) {
|
|
61
|
+
if (this.animation_start === 0) {
|
|
62
|
+
this.animation_start = Date.now();
|
|
63
|
+
}
|
|
64
|
+
const frame = this.getFrame();
|
|
65
|
+
if (frame >= 0 && frame < this.frames.length) {
|
|
66
|
+
return this.frames[frame].canvas;
|
|
67
|
+
}
|
|
68
|
+
return this.frames[0].canvas;
|
|
69
|
+
}
|
|
70
|
+
return this.frames[0].canvas;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function resolveFrameCount(imgWidth, imgHeight, requested) {
|
|
74
|
+
if (imgWidth > 0 && imgHeight % imgWidth === 0) {
|
|
75
|
+
const available = imgHeight / imgWidth;
|
|
76
|
+
return Math.min(requested, available);
|
|
77
|
+
}
|
|
78
|
+
return requested;
|
|
79
|
+
}
|
|
80
|
+
__name(resolveFrameCount, "resolveFrameCount");
|
|
81
|
+
function LoadSprite(url, properties, loaded) {
|
|
82
|
+
const sprite = new Sprite(0, 0);
|
|
83
|
+
sprite.ready = 0;
|
|
84
|
+
const img = new window.Image();
|
|
85
|
+
if (location.protocol !== "file:") {
|
|
86
|
+
img.crossOrigin = "Anonymous";
|
|
87
|
+
}
|
|
88
|
+
img.src = url;
|
|
89
|
+
img.onload = () => {
|
|
90
|
+
sprite.ready = 1;
|
|
91
|
+
if (img.width > 0 && img.height > 0) {
|
|
92
|
+
if (properties?.fps) {
|
|
93
|
+
sprite.fps = properties.fps;
|
|
94
|
+
}
|
|
95
|
+
sprite.width = img.width;
|
|
96
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
97
|
+
sprite.height = Math.round(img.height / numframes);
|
|
98
|
+
sprite.frames = [];
|
|
99
|
+
for (let i = 0; i < numframes; i++) {
|
|
100
|
+
const frame = new Image(sprite.width, sprite.height);
|
|
101
|
+
frame.initContext();
|
|
102
|
+
const ctx = frame.context;
|
|
103
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
104
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
105
|
+
sprite.frames.push(frame);
|
|
106
|
+
}
|
|
107
|
+
sprite.ready = 1;
|
|
108
|
+
}
|
|
109
|
+
if (loaded) {
|
|
110
|
+
loaded();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
img.onerror = () => {
|
|
114
|
+
sprite.ready = 1;
|
|
115
|
+
};
|
|
116
|
+
return sprite;
|
|
117
|
+
}
|
|
118
|
+
__name(LoadSprite, "LoadSprite");
|
|
119
|
+
function UpdateSprite(sprite, img, properties) {
|
|
120
|
+
if (img.width > 0 && img.height > 0) {
|
|
121
|
+
if (properties?.fps) {
|
|
122
|
+
sprite.fps = properties.fps;
|
|
123
|
+
}
|
|
124
|
+
sprite.width = img.width;
|
|
125
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
126
|
+
sprite.height = Math.round(img.height / numframes);
|
|
127
|
+
sprite.frames = [];
|
|
128
|
+
for (let i = 0; i < numframes; i++) {
|
|
129
|
+
const frame = new Image(sprite.width, sprite.height);
|
|
130
|
+
frame.initContext();
|
|
131
|
+
const ctx = frame.context;
|
|
132
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
133
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
134
|
+
sprite.frames.push(frame);
|
|
135
|
+
}
|
|
136
|
+
sprite.ready = 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
__name(UpdateSprite, "UpdateSprite");
|
|
140
|
+
export {
|
|
141
|
+
LoadSprite,
|
|
142
|
+
Sprite,
|
|
143
|
+
UpdateSprite
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sprite.ts"],"sourcesContent":["/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Image","Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Image } from '@al8b/image';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sprite - Animated sprite class for game graphics
|
|
5
|
+
*
|
|
6
|
+
* Provides sprite animation support with multiple frames.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface SpriteProperties {
|
|
10
|
+
frames?: number;
|
|
11
|
+
fps?: number;
|
|
12
|
+
}
|
|
13
|
+
declare class Sprite {
|
|
14
|
+
name: string;
|
|
15
|
+
frames: Image[];
|
|
16
|
+
animation_start: number;
|
|
17
|
+
fps: number;
|
|
18
|
+
ready: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
constructor(width: number, height: number);
|
|
22
|
+
/**
|
|
23
|
+
* Set animation frames per second
|
|
24
|
+
* Preserves current animation position when changing FPS
|
|
25
|
+
*/
|
|
26
|
+
setFPS(fps: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Set the current animation frame
|
|
29
|
+
*/
|
|
30
|
+
setFrame(frame: number): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get the current animation frame index
|
|
33
|
+
*/
|
|
34
|
+
getFrame(): number;
|
|
35
|
+
/**
|
|
36
|
+
* Get the canvas of the current frame
|
|
37
|
+
*/
|
|
38
|
+
getCurrentFrameCanvas(): HTMLCanvasElement | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load a sprite from a URL
|
|
42
|
+
* Supports multi-frame spritesheets (vertical stacking)
|
|
43
|
+
*/
|
|
44
|
+
declare function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite;
|
|
45
|
+
/**
|
|
46
|
+
* Update a sprite from an image element
|
|
47
|
+
* Supports multi-frame spritesheets (vertical stacking)
|
|
48
|
+
*/
|
|
49
|
+
declare function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void;
|
|
50
|
+
|
|
51
|
+
export { LoadSprite, Sprite, type SpriteProperties, UpdateSprite };
|
package/dist/sprite.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Image } from '@al8b/image';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sprite - Animated sprite class for game graphics
|
|
5
|
+
*
|
|
6
|
+
* Provides sprite animation support with multiple frames.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface SpriteProperties {
|
|
10
|
+
frames?: number;
|
|
11
|
+
fps?: number;
|
|
12
|
+
}
|
|
13
|
+
declare class Sprite {
|
|
14
|
+
name: string;
|
|
15
|
+
frames: Image[];
|
|
16
|
+
animation_start: number;
|
|
17
|
+
fps: number;
|
|
18
|
+
ready: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
constructor(width: number, height: number);
|
|
22
|
+
/**
|
|
23
|
+
* Set animation frames per second
|
|
24
|
+
* Preserves current animation position when changing FPS
|
|
25
|
+
*/
|
|
26
|
+
setFPS(fps: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Set the current animation frame
|
|
29
|
+
*/
|
|
30
|
+
setFrame(frame: number): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get the current animation frame index
|
|
33
|
+
*/
|
|
34
|
+
getFrame(): number;
|
|
35
|
+
/**
|
|
36
|
+
* Get the canvas of the current frame
|
|
37
|
+
*/
|
|
38
|
+
getCurrentFrameCanvas(): HTMLCanvasElement | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load a sprite from a URL
|
|
42
|
+
* Supports multi-frame spritesheets (vertical stacking)
|
|
43
|
+
*/
|
|
44
|
+
declare function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite;
|
|
45
|
+
/**
|
|
46
|
+
* Update a sprite from an image element
|
|
47
|
+
* Supports multi-frame spritesheets (vertical stacking)
|
|
48
|
+
*/
|
|
49
|
+
declare function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void;
|
|
50
|
+
|
|
51
|
+
export { LoadSprite, Sprite, type SpriteProperties, UpdateSprite };
|
package/dist/sprite.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/sprite.ts
|
|
22
|
+
var sprite_exports = {};
|
|
23
|
+
__export(sprite_exports, {
|
|
24
|
+
LoadSprite: () => LoadSprite,
|
|
25
|
+
Sprite: () => Sprite,
|
|
26
|
+
UpdateSprite: () => UpdateSprite
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(sprite_exports);
|
|
29
|
+
var import_image = require("@al8b/image");
|
|
30
|
+
var Sprite = class {
|
|
31
|
+
static {
|
|
32
|
+
__name(this, "Sprite");
|
|
33
|
+
}
|
|
34
|
+
name = "";
|
|
35
|
+
frames = [];
|
|
36
|
+
animation_start = 0;
|
|
37
|
+
fps = 5;
|
|
38
|
+
ready = 0;
|
|
39
|
+
width;
|
|
40
|
+
height;
|
|
41
|
+
constructor(width, height) {
|
|
42
|
+
this.width = width;
|
|
43
|
+
this.height = height;
|
|
44
|
+
this.frames = [];
|
|
45
|
+
this.animation_start = 0;
|
|
46
|
+
this.fps = 5;
|
|
47
|
+
if (this.width > 0 && this.height > 0) {
|
|
48
|
+
this.frames.push(new import_image.Image(this.width, this.height));
|
|
49
|
+
this.ready = 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set animation frames per second
|
|
54
|
+
* Preserves current animation position when changing FPS
|
|
55
|
+
*/
|
|
56
|
+
setFPS(fps) {
|
|
57
|
+
const dt = 1e3 / this.fps;
|
|
58
|
+
const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
|
|
59
|
+
this.fps = fps;
|
|
60
|
+
const newDt = 1e3 / fps;
|
|
61
|
+
this.animation_start = Date.now() - frame * newDt;
|
|
62
|
+
return fps;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set the current animation frame
|
|
66
|
+
*/
|
|
67
|
+
setFrame(frame) {
|
|
68
|
+
this.animation_start = Date.now() - 1e3 / this.fps * frame;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the current animation frame index
|
|
72
|
+
*/
|
|
73
|
+
getFrame() {
|
|
74
|
+
const dt = 1e3 / this.fps;
|
|
75
|
+
return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get the canvas of the current frame
|
|
79
|
+
*/
|
|
80
|
+
getCurrentFrameCanvas() {
|
|
81
|
+
if (!this.ready || this.frames.length === 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (this.frames.length > 1) {
|
|
85
|
+
if (this.animation_start === 0) {
|
|
86
|
+
this.animation_start = Date.now();
|
|
87
|
+
}
|
|
88
|
+
const frame = this.getFrame();
|
|
89
|
+
if (frame >= 0 && frame < this.frames.length) {
|
|
90
|
+
return this.frames[frame].canvas;
|
|
91
|
+
}
|
|
92
|
+
return this.frames[0].canvas;
|
|
93
|
+
}
|
|
94
|
+
return this.frames[0].canvas;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
function resolveFrameCount(imgWidth, imgHeight, requested) {
|
|
98
|
+
if (imgWidth > 0 && imgHeight % imgWidth === 0) {
|
|
99
|
+
const available = imgHeight / imgWidth;
|
|
100
|
+
return Math.min(requested, available);
|
|
101
|
+
}
|
|
102
|
+
return requested;
|
|
103
|
+
}
|
|
104
|
+
__name(resolveFrameCount, "resolveFrameCount");
|
|
105
|
+
function LoadSprite(url, properties, loaded) {
|
|
106
|
+
const sprite = new Sprite(0, 0);
|
|
107
|
+
sprite.ready = 0;
|
|
108
|
+
const img = new window.Image();
|
|
109
|
+
if (location.protocol !== "file:") {
|
|
110
|
+
img.crossOrigin = "Anonymous";
|
|
111
|
+
}
|
|
112
|
+
img.src = url;
|
|
113
|
+
img.onload = () => {
|
|
114
|
+
sprite.ready = 1;
|
|
115
|
+
if (img.width > 0 && img.height > 0) {
|
|
116
|
+
if (properties?.fps) {
|
|
117
|
+
sprite.fps = properties.fps;
|
|
118
|
+
}
|
|
119
|
+
sprite.width = img.width;
|
|
120
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
121
|
+
sprite.height = Math.round(img.height / numframes);
|
|
122
|
+
sprite.frames = [];
|
|
123
|
+
for (let i = 0; i < numframes; i++) {
|
|
124
|
+
const frame = new import_image.Image(sprite.width, sprite.height);
|
|
125
|
+
frame.initContext();
|
|
126
|
+
const ctx = frame.context;
|
|
127
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
128
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
129
|
+
sprite.frames.push(frame);
|
|
130
|
+
}
|
|
131
|
+
sprite.ready = 1;
|
|
132
|
+
}
|
|
133
|
+
if (loaded) {
|
|
134
|
+
loaded();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
img.onerror = () => {
|
|
138
|
+
sprite.ready = 1;
|
|
139
|
+
};
|
|
140
|
+
return sprite;
|
|
141
|
+
}
|
|
142
|
+
__name(LoadSprite, "LoadSprite");
|
|
143
|
+
function UpdateSprite(sprite, img, properties) {
|
|
144
|
+
if (img.width > 0 && img.height > 0) {
|
|
145
|
+
if (properties?.fps) {
|
|
146
|
+
sprite.fps = properties.fps;
|
|
147
|
+
}
|
|
148
|
+
sprite.width = img.width;
|
|
149
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
150
|
+
sprite.height = Math.round(img.height / numframes);
|
|
151
|
+
sprite.frames = [];
|
|
152
|
+
for (let i = 0; i < numframes; i++) {
|
|
153
|
+
const frame = new import_image.Image(sprite.width, sprite.height);
|
|
154
|
+
frame.initContext();
|
|
155
|
+
const ctx = frame.context;
|
|
156
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
157
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
158
|
+
sprite.frames.push(frame);
|
|
159
|
+
}
|
|
160
|
+
sprite.ready = 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
__name(UpdateSprite, "UpdateSprite");
|
|
164
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
165
|
+
0 && (module.exports = {
|
|
166
|
+
LoadSprite,
|
|
167
|
+
Sprite,
|
|
168
|
+
UpdateSprite
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=sprite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sprite.ts"],"sourcesContent":["/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
|
package/dist/sprite.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/sprite.ts
|
|
5
|
+
import { Image } from "@al8b/image";
|
|
6
|
+
var Sprite = class {
|
|
7
|
+
static {
|
|
8
|
+
__name(this, "Sprite");
|
|
9
|
+
}
|
|
10
|
+
name = "";
|
|
11
|
+
frames = [];
|
|
12
|
+
animation_start = 0;
|
|
13
|
+
fps = 5;
|
|
14
|
+
ready = 0;
|
|
15
|
+
width;
|
|
16
|
+
height;
|
|
17
|
+
constructor(width, height) {
|
|
18
|
+
this.width = width;
|
|
19
|
+
this.height = height;
|
|
20
|
+
this.frames = [];
|
|
21
|
+
this.animation_start = 0;
|
|
22
|
+
this.fps = 5;
|
|
23
|
+
if (this.width > 0 && this.height > 0) {
|
|
24
|
+
this.frames.push(new Image(this.width, this.height));
|
|
25
|
+
this.ready = 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Set animation frames per second
|
|
30
|
+
* Preserves current animation position when changing FPS
|
|
31
|
+
*/
|
|
32
|
+
setFPS(fps) {
|
|
33
|
+
const dt = 1e3 / this.fps;
|
|
34
|
+
const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
|
|
35
|
+
this.fps = fps;
|
|
36
|
+
const newDt = 1e3 / fps;
|
|
37
|
+
this.animation_start = Date.now() - frame * newDt;
|
|
38
|
+
return fps;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Set the current animation frame
|
|
42
|
+
*/
|
|
43
|
+
setFrame(frame) {
|
|
44
|
+
this.animation_start = Date.now() - 1e3 / this.fps * frame;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the current animation frame index
|
|
48
|
+
*/
|
|
49
|
+
getFrame() {
|
|
50
|
+
const dt = 1e3 / this.fps;
|
|
51
|
+
return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the canvas of the current frame
|
|
55
|
+
*/
|
|
56
|
+
getCurrentFrameCanvas() {
|
|
57
|
+
if (!this.ready || this.frames.length === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (this.frames.length > 1) {
|
|
61
|
+
if (this.animation_start === 0) {
|
|
62
|
+
this.animation_start = Date.now();
|
|
63
|
+
}
|
|
64
|
+
const frame = this.getFrame();
|
|
65
|
+
if (frame >= 0 && frame < this.frames.length) {
|
|
66
|
+
return this.frames[frame].canvas;
|
|
67
|
+
}
|
|
68
|
+
return this.frames[0].canvas;
|
|
69
|
+
}
|
|
70
|
+
return this.frames[0].canvas;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function resolveFrameCount(imgWidth, imgHeight, requested) {
|
|
74
|
+
if (imgWidth > 0 && imgHeight % imgWidth === 0) {
|
|
75
|
+
const available = imgHeight / imgWidth;
|
|
76
|
+
return Math.min(requested, available);
|
|
77
|
+
}
|
|
78
|
+
return requested;
|
|
79
|
+
}
|
|
80
|
+
__name(resolveFrameCount, "resolveFrameCount");
|
|
81
|
+
function LoadSprite(url, properties, loaded) {
|
|
82
|
+
const sprite = new Sprite(0, 0);
|
|
83
|
+
sprite.ready = 0;
|
|
84
|
+
const img = new window.Image();
|
|
85
|
+
if (location.protocol !== "file:") {
|
|
86
|
+
img.crossOrigin = "Anonymous";
|
|
87
|
+
}
|
|
88
|
+
img.src = url;
|
|
89
|
+
img.onload = () => {
|
|
90
|
+
sprite.ready = 1;
|
|
91
|
+
if (img.width > 0 && img.height > 0) {
|
|
92
|
+
if (properties?.fps) {
|
|
93
|
+
sprite.fps = properties.fps;
|
|
94
|
+
}
|
|
95
|
+
sprite.width = img.width;
|
|
96
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
97
|
+
sprite.height = Math.round(img.height / numframes);
|
|
98
|
+
sprite.frames = [];
|
|
99
|
+
for (let i = 0; i < numframes; i++) {
|
|
100
|
+
const frame = new Image(sprite.width, sprite.height);
|
|
101
|
+
frame.initContext();
|
|
102
|
+
const ctx = frame.context;
|
|
103
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
104
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
105
|
+
sprite.frames.push(frame);
|
|
106
|
+
}
|
|
107
|
+
sprite.ready = 1;
|
|
108
|
+
}
|
|
109
|
+
if (loaded) {
|
|
110
|
+
loaded();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
img.onerror = () => {
|
|
114
|
+
sprite.ready = 1;
|
|
115
|
+
};
|
|
116
|
+
return sprite;
|
|
117
|
+
}
|
|
118
|
+
__name(LoadSprite, "LoadSprite");
|
|
119
|
+
function UpdateSprite(sprite, img, properties) {
|
|
120
|
+
if (img.width > 0 && img.height > 0) {
|
|
121
|
+
if (properties?.fps) {
|
|
122
|
+
sprite.fps = properties.fps;
|
|
123
|
+
}
|
|
124
|
+
sprite.width = img.width;
|
|
125
|
+
const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
|
|
126
|
+
sprite.height = Math.round(img.height / numframes);
|
|
127
|
+
sprite.frames = [];
|
|
128
|
+
for (let i = 0; i < numframes; i++) {
|
|
129
|
+
const frame = new Image(sprite.width, sprite.height);
|
|
130
|
+
frame.initContext();
|
|
131
|
+
const ctx = frame.context;
|
|
132
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
133
|
+
ctx.drawImage(img, 0, -i * sprite.height);
|
|
134
|
+
sprite.frames.push(frame);
|
|
135
|
+
}
|
|
136
|
+
sprite.ready = 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
__name(UpdateSprite, "UpdateSprite");
|
|
140
|
+
export {
|
|
141
|
+
LoadSprite,
|
|
142
|
+
Sprite,
|
|
143
|
+
UpdateSprite
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=sprite.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sprite.ts"],"sourcesContent":["/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":";;;;AAMA,SAASA,aAAa;AAOf,IAAMC,SAAN,MAAMA;EAbb,OAaaA;;;EACLC,OAAe;EACfC,SAAkB,CAAA;EAClBC,kBAA0B;EAC1BC,MAAc;EACdC,QAAgB;EAChBC;EACAC;EAEP,YAAYD,OAAeC,QAAgB;AAC1C,SAAKD,QAAQA;AACb,SAAKC,SAASA;AACd,SAAKL,SAAS,CAAA;AACd,SAAKC,kBAAkB;AACvB,SAAKC,MAAM;AAEX,QAAI,KAAKE,QAAQ,KAAK,KAAKC,SAAS,GAAG;AACtC,WAAKL,OAAOM,KAAK,IAAIC,MAAM,KAAKH,OAAO,KAAKC,MAAM,CAAA;AAClD,WAAKF,QAAQ;IACd;EACD;;;;;EAMAK,OAAON,KAAqB;AAC3B,UAAMO,KAAK,MAAO,KAAKP;AACvB,UAAMQ,SAAUC,KAAKC,IAAG,IAAK,KAAKX,mBAAmBQ,KAAM,KAAKT,OAAOa;AACvE,SAAKX,MAAMA;AACX,UAAMY,QAAQ,MAAOZ;AACrB,SAAKD,kBAAkBU,KAAKC,IAAG,IAAKF,QAAQI;AAC5C,WAAOZ;EACR;;;;EAKAa,SAASL,OAAqB;AAC7B,SAAKT,kBAAkBU,KAAKC,IAAG,IAAM,MAAO,KAAKV,MAAOQ;EACzD;;;;EAKAM,WAAmB;AAClB,UAAMP,KAAK,MAAO,KAAKP;AACvB,WAAOe,KAAKC,OAAOP,KAAKC,IAAG,IAAK,KAAKX,mBAAmBQ,EAAAA,IAAM,KAAKT,OAAOa;EAC3E;;;;EAKAM,wBAAkD;AACjD,QAAI,CAAC,KAAKhB,SAAS,KAAKH,OAAOa,WAAW,GAAG;AAC5C,aAAO;IACR;AAEA,QAAI,KAAKb,OAAOa,SAAS,GAAG;AAC3B,UAAI,KAAKZ,oBAAoB,GAAG;AAC/B,aAAKA,kBAAkBU,KAAKC,IAAG;MAChC;AACA,YAAMF,QAAQ,KAAKM,SAAQ;AAC3B,UAAIN,SAAS,KAAKA,QAAQ,KAAKV,OAAOa,QAAQ;AAC7C,eAAO,KAAKb,OAAOU,KAAAA,EAAOU;MAC3B;AACA,aAAO,KAAKpB,OAAO,CAAA,EAAGoB;IACvB;AAEA,WAAO,KAAKpB,OAAO,CAAA,EAAGoB;EACvB;AACD;AAYA,SAASC,kBAAkBC,UAAkBC,WAAmBC,WAAiB;AAChF,MAAIF,WAAW,KAAKC,YAAYD,aAAa,GAAG;AAC/C,UAAMG,YAAYF,YAAYD;AAC9B,WAAOL,KAAKS,IAAIF,WAAWC,SAAAA;EAC5B;AACA,SAAOD;AACR;AANSH;AAYF,SAASM,WAAWC,KAAaC,YAA+BC,QAAmB;AACzF,QAAMC,SAAS,IAAIjC,OAAO,GAAG,CAAA;AAC7BiC,SAAO5B,QAAQ;AAEf,QAAM6B,MAAM,IAAIC,OAAO1B,MAAK;AAC5B,MAAI2B,SAASC,aAAa,SAAS;AAClCH,QAAII,cAAc;EACnB;AACAJ,MAAIK,MAAMT;AAEVI,MAAIM,SAAS,MAAA;AACZP,WAAO5B,QAAQ;AAEf,QAAI6B,IAAI5B,QAAQ,KAAK4B,IAAI3B,SAAS,GAAG;AACpC,UAAIwB,YAAY3B,KAAK;AACpB6B,eAAO7B,MAAM2B,WAAW3B;MACzB;AAEA6B,aAAO3B,QAAQ4B,IAAI5B;AACnB,YAAMmC,YAAYlB,kBAAkBW,IAAI5B,OAAO4B,IAAI3B,QAAQwB,YAAY7B,UAAU,CAAA;AACjF+B,aAAO1B,SAASY,KAAKuB,MAAMR,IAAI3B,SAASkC,SAAAA;AACxCR,aAAO/B,SAAS,CAAA;AAEhB,eAASyC,IAAI,GAAGA,IAAIF,WAAWE,KAAK;AACnC,cAAM/B,QAAQ,IAAIH,MAAMwB,OAAO3B,OAAO2B,OAAO1B,MAAM;AACnDK,cAAMgC,YAAW;AACjB,cAAMC,MAAMjC,MAAMkC;AAClBD,YAAIE,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAChCF,YAAIG,UAAUd,KAAK,GAAG,CAACS,IAAIV,OAAO1B,MAAM;AACxC0B,eAAO/B,OAAOM,KAAKI,KAAAA;MACpB;AACAqB,aAAO5B,QAAQ;IAChB;AAEA,QAAI2B,QAAQ;AACXA,aAAAA;IACD;EACD;AAEAE,MAAIe,UAAU,MAAA;AACbhB,WAAO5B,QAAQ;EAChB;AAEA,SAAO4B;AACR;AA5CgBJ;AAkDT,SAASqB,aAAajB,QAAgBC,KAAuBH,YAA6B;AAChG,MAAIG,IAAI5B,QAAQ,KAAK4B,IAAI3B,SAAS,GAAG;AACpC,QAAIwB,YAAY3B,KAAK;AACpB6B,aAAO7B,MAAM2B,WAAW3B;IACzB;AACA6B,WAAO3B,QAAQ4B,IAAI5B;AACnB,UAAMmC,YAAYlB,kBAAkBW,IAAI5B,OAAO4B,IAAI3B,QAAQwB,YAAY7B,UAAU,CAAA;AACjF+B,WAAO1B,SAASY,KAAKuB,MAAMR,IAAI3B,SAASkC,SAAAA;AACxCR,WAAO/B,SAAS,CAAA;AAEhB,aAASyC,IAAI,GAAGA,IAAIF,WAAWE,KAAK;AACnC,YAAM/B,QAAQ,IAAIH,MAAMwB,OAAO3B,OAAO2B,OAAO1B,MAAM;AACnDK,YAAMgC,YAAW;AACjB,YAAMC,MAAMjC,MAAMkC;AAClBD,UAAIE,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAChCF,UAAIG,UAAUd,KAAK,GAAG,CAACS,IAAIV,OAAO1B,MAAM;AACxC0B,aAAO/B,OAAOM,KAAKI,KAAAA;IACpB;AACAqB,WAAO5B,QAAQ;EAChB;AACD;AApBgB6C;","names":["Image","Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@al8b/sprites",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"files": [
|
|
6
|
+
"dist/**/*",
|
|
7
|
+
"README.md",
|
|
8
|
+
"package.json"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"clean": "bun --bun ../../../scripts/clean-package.mjs dist",
|
|
13
|
+
"test": "vitest run --passWithNoTests"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.mjs",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.mjs",
|
|
22
|
+
"require": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@al8b/diagnostics": "workspace:*",
|
|
27
|
+
"@al8b/image": "workspace:*"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"sprites"
|
|
31
|
+
],
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/jsdom": "^27.0.0",
|
|
34
|
+
"@vitest/ui": "^4.0.9",
|
|
35
|
+
"happy-dom": "^20.0.10",
|
|
36
|
+
"jsdom": "^24.1.3",
|
|
37
|
+
"vitest": "^4.0.9"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|