@unboxy/phaser-sdk 0.2.33 → 0.2.35
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/HudRuntime.js +27 -2
- package/dist/scene/SceneLoader.js +63 -1
- package/dist/scene/spawnEntity.js +35 -1
- package/dist/scene/types.d.ts +29 -0
- package/package.json +1 -1
package/dist/scene/HudRuntime.js
CHANGED
|
@@ -140,7 +140,27 @@ function createText(ctx, entity, pos) {
|
|
|
140
140
|
return text;
|
|
141
141
|
}
|
|
142
142
|
function createImage(ctx, entity, pos) {
|
|
143
|
-
|
|
143
|
+
let asset;
|
|
144
|
+
try {
|
|
145
|
+
asset = ctx.resolveAsset(entity.visual.assetId);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Soft-fail when the assetId isn't in the manifest. Mirrors the world
|
|
149
|
+
// scene's createSprite missing-asset path. Wraps a Graphics in a
|
|
150
|
+
// Container so the editor's bounds-based hit-test has a sized target.
|
|
151
|
+
const w = entity.visual.width ?? 64;
|
|
152
|
+
const h = entity.visual.height ?? 64;
|
|
153
|
+
const g = ctx.scene.add.graphics();
|
|
154
|
+
g.fillStyle(0x4d2244, 0.4);
|
|
155
|
+
g.fillRect(-w / 2, -h / 2, w, h);
|
|
156
|
+
g.lineStyle(2, 0xff00ff, 0.9);
|
|
157
|
+
g.strokeRect(-w / 2, -h / 2, w, h);
|
|
158
|
+
g.lineBetween(-w / 2, -h / 2, w / 2, h / 2);
|
|
159
|
+
g.lineBetween(w / 2, -h / 2, -w / 2, h / 2);
|
|
160
|
+
const container = ctx.scene.add.container(pos.x, pos.y, [g]);
|
|
161
|
+
container.setSize(w, h);
|
|
162
|
+
return container;
|
|
163
|
+
}
|
|
144
164
|
const image = entity.visual.frame !== undefined
|
|
145
165
|
? ctx.scene.add.image(pos.x, pos.y, asset.textureKey, entity.visual.frame)
|
|
146
166
|
: ctx.scene.add.image(pos.x, pos.y, asset.textureKey);
|
|
@@ -478,7 +498,12 @@ export function preloadHudAssets(scene, hudScene, manifest) {
|
|
|
478
498
|
for (const id of ids) {
|
|
479
499
|
const asset = manifest.assets?.find((a) => a.id === id);
|
|
480
500
|
if (!asset) {
|
|
481
|
-
|
|
501
|
+
// Soft-fail: warn + continue. createImage / createIconButton render
|
|
502
|
+
// a placeholder when the asset is unresolvable. Mirrors the world
|
|
503
|
+
// scene's preloadSceneAssets soft-fail (SceneLoader.ts).
|
|
504
|
+
// eslint-disable-next-line no-console
|
|
505
|
+
console.warn(`[unboxy/hud] HUD scene '${hudScene.id}' references assetId '${id}' but the manifest has no such asset; widget will render as a placeholder`);
|
|
506
|
+
continue;
|
|
482
507
|
}
|
|
483
508
|
if (scene.textures.exists(asset.textureKey))
|
|
484
509
|
continue;
|
|
@@ -84,7 +84,15 @@ export function preloadSceneAssets(scene, sceneFile, manifest) {
|
|
|
84
84
|
continue;
|
|
85
85
|
const asset = state.assetsById.get(id);
|
|
86
86
|
if (!asset) {
|
|
87
|
-
|
|
87
|
+
// Soft-fail: warn, continue. The downstream spawn path renders a
|
|
88
|
+
// magenta "?" placeholder for the offending entity so the user sees
|
|
89
|
+
// *where* a missing asset would have been, without losing every
|
|
90
|
+
// other entity in the scene. Common cause is the editor adding a
|
|
91
|
+
// sprite that references an asset whose manifest entry never got
|
|
92
|
+
// synced (e.g. AssetShelf drag without manifest update).
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.warn(`[unboxy/scene] scene '${sceneFile.id}' references assetId '${id}' but the manifest has no such asset; entity will render as a placeholder`);
|
|
95
|
+
continue;
|
|
88
96
|
}
|
|
89
97
|
queueAssetLoad(scene, asset);
|
|
90
98
|
state.requestedAssetIds.add(id);
|
|
@@ -107,6 +115,56 @@ function collectAssetIds(entities) {
|
|
|
107
115
|
walk(e);
|
|
108
116
|
return Array.from(ids);
|
|
109
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
|
+
}
|
|
110
168
|
function queueAssetLoad(scene, asset) {
|
|
111
169
|
if (scene.textures.exists(asset.textureKey))
|
|
112
170
|
return;
|
|
@@ -174,6 +232,10 @@ export async function loadWorldScene(scene, sceneId, options = {}) {
|
|
|
174
232
|
// Lazy preload of any new assets this scene needs, then await loader.
|
|
175
233
|
preloadSceneAssets(scene, sceneFile, manifest);
|
|
176
234
|
await runLoader(scene);
|
|
235
|
+
// Register Phaser animations declared in `manifest.assets[].animations`.
|
|
236
|
+
// Done after texture load + before entity spawn so behavior code can call
|
|
237
|
+
// `sprite.play('walk-down')` from frame 1 without manual `anims.create()`.
|
|
238
|
+
registerSheetAnimations(scene, manifest);
|
|
177
239
|
// Spawn entities.
|
|
178
240
|
const registry = attachEntityRegistry(scene);
|
|
179
241
|
const ctx = {
|
|
@@ -36,7 +36,18 @@ export function spawnEntity(ctx, entity) {
|
|
|
36
36
|
}
|
|
37
37
|
function createSprite(ctx, entity) {
|
|
38
38
|
const v = entity.visual;
|
|
39
|
-
|
|
39
|
+
let asset;
|
|
40
|
+
try {
|
|
41
|
+
asset = ctx.resolveAsset(v.assetId);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
// Soft-fail when the assetId isn't in the manifest. SceneLoader's
|
|
45
|
+
// preloadSceneAssets logged the warning; here we render a clear
|
|
46
|
+
// magenta-bordered "?" placeholder so the entity is still visible
|
|
47
|
+
// and selectable in the editor. Same pattern as createCodeRendered's
|
|
48
|
+
// missing-render-script path.
|
|
49
|
+
return missingAssetPlaceholder(ctx, v.assetId);
|
|
50
|
+
}
|
|
40
51
|
// Phaser's add.sprite handles both plain images and atlas/spritesheet
|
|
41
52
|
// textures uniformly when given (key, frame). For plain images, frame
|
|
42
53
|
// is undefined and Phaser uses the default frame.
|
|
@@ -51,6 +62,29 @@ function createSprite(ctx, entity) {
|
|
|
51
62
|
sprite.setFlipY(true);
|
|
52
63
|
return sprite;
|
|
53
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Magenta "?" placeholder for an entity whose assetId isn't in the manifest.
|
|
67
|
+
* Drawn as a Graphics so the editor's hit-test (which uses getBounds) lands
|
|
68
|
+
* on the visible square. Mirrors createCodeRendered's missing-script path.
|
|
69
|
+
*/
|
|
70
|
+
function missingAssetPlaceholder(ctx, assetId) {
|
|
71
|
+
const w = 64;
|
|
72
|
+
const h = 64;
|
|
73
|
+
const g = ctx.scene.add.graphics();
|
|
74
|
+
g.fillStyle(0x4d2244, 0.4);
|
|
75
|
+
g.fillRect(-w / 2, -h / 2, w, h);
|
|
76
|
+
g.lineStyle(2, 0xff00ff, 0.9);
|
|
77
|
+
g.strokeRect(-w / 2, -h / 2, w, h);
|
|
78
|
+
// Diagonal "no entry" lines + a "?" text so it's obvious from a glance.
|
|
79
|
+
g.lineBetween(-w / 2, -h / 2, w / 2, h / 2);
|
|
80
|
+
g.lineBetween(w / 2, -h / 2, -w / 2, h / 2);
|
|
81
|
+
// Phaser Graphics has no intrinsic size — sizeForHitTest sets bounds so
|
|
82
|
+
// the editor's hit-test (which uses getBounds) can find this entity.
|
|
83
|
+
sizeForHitTest(g, w, h);
|
|
84
|
+
g.setDataEnabled();
|
|
85
|
+
g.setData('unboxyMissingAssetId', assetId);
|
|
86
|
+
return g;
|
|
87
|
+
}
|
|
54
88
|
function createPrimitive(ctx, entity) {
|
|
55
89
|
const v = entity.visual;
|
|
56
90
|
if (v.kind === 'rect')
|
package/dist/scene/types.d.ts
CHANGED
|
@@ -46,6 +46,35 @@ export interface AssetRecord {
|
|
|
46
46
|
atlasPath?: string;
|
|
47
47
|
/** `'xml'` (Starling) or `'json'` (TexturePacker). */
|
|
48
48
|
atlasFormat?: 'xml' | 'json';
|
|
49
|
+
/**
|
|
50
|
+
* For `spritesheet`: named animation ranges. SDK auto-registers these as
|
|
51
|
+
* Phaser anims at preload, so behavior code can call
|
|
52
|
+
* `sprite.play('walk-down')` without manual `anims.create()` setup.
|
|
53
|
+
*
|
|
54
|
+
* <p>Pack import populates this from vision detection (4-direction RPG /
|
|
55
|
+
* sidescroller / multi-action layouts) or from Aseprite `frameTags`. The
|
|
56
|
+
* agent can also add/edit entries by hand when wiring a sheet asset.
|
|
57
|
+
*
|
|
58
|
+
* <p>Animation keys are scene-global in Phaser; if two sheets register
|
|
59
|
+
* the same name, the second registration is skipped with a warning.
|
|
60
|
+
* Disambiguate by namespacing the name (e.g. `cow-walk-down`) when
|
|
61
|
+
* collisions matter.
|
|
62
|
+
*/
|
|
63
|
+
animations?: SheetAnimation[];
|
|
64
|
+
/** Default playback rate for `animations[]`. Defaults to 8. */
|
|
65
|
+
fps?: number;
|
|
66
|
+
}
|
|
67
|
+
export interface SheetAnimation {
|
|
68
|
+
/** Phaser animation key — used as `sprite.play('<name>')`. */
|
|
69
|
+
name: string;
|
|
70
|
+
/** First frame index, inclusive. */
|
|
71
|
+
from: number;
|
|
72
|
+
/** Last frame index, inclusive. */
|
|
73
|
+
to: number;
|
|
74
|
+
/** When true, Phaser registers with `repeat: -1`. Defaults to true. */
|
|
75
|
+
loop?: boolean;
|
|
76
|
+
/** Override the asset-level fps for this specific animation. */
|
|
77
|
+
fps?: number;
|
|
49
78
|
}
|
|
50
79
|
export interface Manifest {
|
|
51
80
|
schemaVersion: number;
|