@unboxy/phaser-sdk 0.2.34 → 0.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scene/SceneLoader.js +75 -4
- package/dist/scene/types.d.ts +38 -0
- package/package.json +1 -1
|
@@ -115,6 +115,56 @@ function collectAssetIds(entities) {
|
|
|
115
115
|
walk(e);
|
|
116
116
|
return Array.from(ids);
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Walk every `kind=spritesheet` asset in the manifest and register each one's
|
|
120
|
+
* `animations[]` as a Phaser animation, so behavior code can call
|
|
121
|
+
* `sprite.play('walk-down')` directly. Idempotent — uses `scene.anims.exists()`
|
|
122
|
+
* to skip duplicates (Phaser's anims.create throws otherwise).
|
|
123
|
+
*
|
|
124
|
+
* <p>Animation keys are scene-global. Two sheets registering the same name
|
|
125
|
+
* (e.g. two characters both with `walk-down`) → first wins, second logs a
|
|
126
|
+
* warning. Sheets that need disambiguation should namespace their keys
|
|
127
|
+
* (e.g. `cow:walk-down`) at metadata time.
|
|
128
|
+
*/
|
|
129
|
+
function registerSheetAnimations(scene, manifest) {
|
|
130
|
+
const assets = manifest.assets ?? [];
|
|
131
|
+
for (const asset of assets) {
|
|
132
|
+
if (asset.kind !== 'spritesheet')
|
|
133
|
+
continue;
|
|
134
|
+
if (!asset.animations || asset.animations.length === 0)
|
|
135
|
+
continue;
|
|
136
|
+
if (!scene.textures.exists(asset.textureKey))
|
|
137
|
+
continue; // not loaded — skip
|
|
138
|
+
const defaultFps = asset.fps && asset.fps > 0 ? asset.fps : 8;
|
|
139
|
+
for (const anim of asset.animations) {
|
|
140
|
+
if (!anim.name)
|
|
141
|
+
continue;
|
|
142
|
+
if (scene.anims.exists(anim.name)) {
|
|
143
|
+
// Don't error out on collision — first registration wins.
|
|
144
|
+
// eslint-disable-next-line no-console
|
|
145
|
+
console.warn(`[unboxy/scene] animation key '${anim.name}' already registered; skipping (asset '${asset.id}'). Disambiguate by renaming in the asset's animations metadata.`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const fps = anim.fps && anim.fps > 0 ? anim.fps : defaultFps;
|
|
149
|
+
const loop = anim.loop !== false; // default true
|
|
150
|
+
try {
|
|
151
|
+
scene.anims.create({
|
|
152
|
+
key: anim.name,
|
|
153
|
+
frames: scene.anims.generateFrameNumbers(asset.textureKey, {
|
|
154
|
+
start: anim.from,
|
|
155
|
+
end: anim.to,
|
|
156
|
+
}),
|
|
157
|
+
frameRate: fps,
|
|
158
|
+
repeat: loop ? -1 : 0,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
// eslint-disable-next-line no-console
|
|
163
|
+
console.warn(`[unboxy/scene] failed to register animation '${anim.name}' on '${asset.id}': ${String(e)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
118
168
|
function queueAssetLoad(scene, asset) {
|
|
119
169
|
if (scene.textures.exists(asset.textureKey))
|
|
120
170
|
return;
|
|
@@ -122,12 +172,29 @@ function queueAssetLoad(scene, asset) {
|
|
|
122
172
|
case 'image':
|
|
123
173
|
scene.load.image(asset.textureKey, asset.path);
|
|
124
174
|
return;
|
|
125
|
-
case 'spritesheet':
|
|
126
|
-
|
|
127
|
-
|
|
175
|
+
case 'spritesheet': {
|
|
176
|
+
// Tolerant config resolution: prefer nested `spriteSheetConfig`, fall
|
|
177
|
+
// back to top-level `frameWidth/frameHeight/margin/spacing` if the
|
|
178
|
+
// agent put them there (common typo — both shapes appear in real
|
|
179
|
+
// workspaces). Soft-fail with warning when neither shape provides
|
|
180
|
+
// frame dims so the rest of the scene still boots.
|
|
181
|
+
const cfg = asset.spriteSheetConfig
|
|
182
|
+
?? (asset.frameWidth && asset.frameHeight
|
|
183
|
+
? {
|
|
184
|
+
frameWidth: asset.frameWidth,
|
|
185
|
+
frameHeight: asset.frameHeight,
|
|
186
|
+
margin: asset.margin,
|
|
187
|
+
spacing: asset.spacing,
|
|
188
|
+
}
|
|
189
|
+
: null);
|
|
190
|
+
if (!cfg) {
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
192
|
+
console.warn(`[unboxy/scene] asset '${asset.id}' kind=spritesheet missing spriteSheetConfig (and no top-level frameWidth/frameHeight); skipping load`);
|
|
193
|
+
return;
|
|
128
194
|
}
|
|
129
|
-
scene.load.spritesheet(asset.textureKey, asset.path,
|
|
195
|
+
scene.load.spritesheet(asset.textureKey, asset.path, cfg);
|
|
130
196
|
return;
|
|
197
|
+
}
|
|
131
198
|
case 'atlas':
|
|
132
199
|
if (!asset.atlasPath || !asset.atlasFormat) {
|
|
133
200
|
throw new Error(`[unboxy/scene] asset '${asset.id}' kind=atlas missing atlasPath/atlasFormat`);
|
|
@@ -182,6 +249,10 @@ export async function loadWorldScene(scene, sceneId, options = {}) {
|
|
|
182
249
|
// Lazy preload of any new assets this scene needs, then await loader.
|
|
183
250
|
preloadSceneAssets(scene, sceneFile, manifest);
|
|
184
251
|
await runLoader(scene);
|
|
252
|
+
// Register Phaser animations declared in `manifest.assets[].animations`.
|
|
253
|
+
// Done after texture load + before entity spawn so behavior code can call
|
|
254
|
+
// `sprite.play('walk-down')` from frame 1 without manual `anims.create()`.
|
|
255
|
+
registerSheetAnimations(scene, manifest);
|
|
185
256
|
// Spawn entities.
|
|
186
257
|
const registry = attachEntityRegistry(scene);
|
|
187
258
|
const ctx = {
|
package/dist/scene/types.d.ts
CHANGED
|
@@ -42,10 +42,48 @@ export interface AssetRecord {
|
|
|
42
42
|
margin?: number;
|
|
43
43
|
spacing?: number;
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Top-level convenience fields. When the agent writes a spritesheet asset
|
|
47
|
+
* entry, it commonly puts these at the top level instead of nested in
|
|
48
|
+
* `spriteSheetConfig` — both shapes are now accepted by the loader.
|
|
49
|
+
*/
|
|
50
|
+
frameWidth?: number;
|
|
51
|
+
frameHeight?: number;
|
|
52
|
+
margin?: number;
|
|
53
|
+
spacing?: number;
|
|
45
54
|
/** For `atlas`: companion atlas file path (xml or json). */
|
|
46
55
|
atlasPath?: string;
|
|
47
56
|
/** `'xml'` (Starling) or `'json'` (TexturePacker). */
|
|
48
57
|
atlasFormat?: 'xml' | 'json';
|
|
58
|
+
/**
|
|
59
|
+
* For `spritesheet`: named animation ranges. SDK auto-registers these as
|
|
60
|
+
* Phaser anims at preload, so behavior code can call
|
|
61
|
+
* `sprite.play('walk-down')` without manual `anims.create()` setup.
|
|
62
|
+
*
|
|
63
|
+
* <p>Pack import populates this from vision detection (4-direction RPG /
|
|
64
|
+
* sidescroller / multi-action layouts) or from Aseprite `frameTags`. The
|
|
65
|
+
* agent can also add/edit entries by hand when wiring a sheet asset.
|
|
66
|
+
*
|
|
67
|
+
* <p>Animation keys are scene-global in Phaser; if two sheets register
|
|
68
|
+
* the same name, the second registration is skipped with a warning.
|
|
69
|
+
* Disambiguate by namespacing the name (e.g. `cow-walk-down`) when
|
|
70
|
+
* collisions matter.
|
|
71
|
+
*/
|
|
72
|
+
animations?: SheetAnimation[];
|
|
73
|
+
/** Default playback rate for `animations[]`. Defaults to 8. */
|
|
74
|
+
fps?: number;
|
|
75
|
+
}
|
|
76
|
+
export interface SheetAnimation {
|
|
77
|
+
/** Phaser animation key — used as `sprite.play('<name>')`. */
|
|
78
|
+
name: string;
|
|
79
|
+
/** First frame index, inclusive. */
|
|
80
|
+
from: number;
|
|
81
|
+
/** Last frame index, inclusive. */
|
|
82
|
+
to: number;
|
|
83
|
+
/** When true, Phaser registers with `repeat: -1`. Defaults to true. */
|
|
84
|
+
loop?: boolean;
|
|
85
|
+
/** Override the asset-level fps for this specific animation. */
|
|
86
|
+
fps?: number;
|
|
49
87
|
}
|
|
50
88
|
export interface Manifest {
|
|
51
89
|
schemaVersion: number;
|