@unboxy/phaser-sdk 0.2.41 → 0.2.43
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/editor/EditorBridge.js +77 -2
- package/dist/protocol.d.ts +15 -0
- package/dist/scene/HudRuntime.js +32 -7
- package/dist/scene/SceneLoader.d.ts +12 -0
- package/dist/scene/SceneLoader.js +50 -0
- package/dist/scene/types.d.ts +16 -2
- package/package.json +1 -1
|
@@ -49,7 +49,7 @@ function handleMessage(game, msg) {
|
|
|
49
49
|
postSceneSnapshot(game);
|
|
50
50
|
break;
|
|
51
51
|
case 'unboxy:editor:applyEdit':
|
|
52
|
-
applyEdit(game, msg.entityId, msg.patch);
|
|
52
|
+
void applyEdit(game, msg.entityId, msg.patch, msg.manifestAsset);
|
|
53
53
|
break;
|
|
54
54
|
case 'unboxy:editor:setSelection':
|
|
55
55
|
setSelection(game, msg.entityIds[0] ?? null);
|
|
@@ -429,7 +429,30 @@ function findRegistry(game) {
|
|
|
429
429
|
return undefined;
|
|
430
430
|
}
|
|
431
431
|
// --- applyEdit ------------------------------------------------------------
|
|
432
|
-
function applyEdit(game, entityId, patch) {
|
|
432
|
+
async function applyEdit(game, entityId, patch, manifestAsset) {
|
|
433
|
+
// Lazy-load + manifest-upsert path. When the host's Inspector picks a Bg
|
|
434
|
+
// image asset that the iframe never preloaded (e.g. a per-game upload that
|
|
435
|
+
// was never dragged into the scene) OR an asset whose `kind` just flipped
|
|
436
|
+
// (e.g. region-atlas — slice 10 — flipping from 'image' to 'atlas'), the
|
|
437
|
+
// host pipelines the new manifest entry along with the patch so the
|
|
438
|
+
// resulting re-spawn sees a consistent runtime state. Without this, the
|
|
439
|
+
// re-spawn reads the stale cached manifest and falls back to the
|
|
440
|
+
// colored-rect placeholder.
|
|
441
|
+
if (manifestAsset) {
|
|
442
|
+
const targetScene = getEditorMode(game) === 'hud'
|
|
443
|
+
? game.scene.getScene(UNBOXY_HUD_SCENE_KEY)
|
|
444
|
+
: findWorldScene(game);
|
|
445
|
+
if (targetScene) {
|
|
446
|
+
try {
|
|
447
|
+
upsertCachedManifestAsset(targetScene, manifestAsset);
|
|
448
|
+
await ensureAssetLoaded(targetScene, manifestAsset);
|
|
449
|
+
}
|
|
450
|
+
catch (e) {
|
|
451
|
+
console.warn('[unboxy/editor] applyEdit asset upsert/load failed:', e);
|
|
452
|
+
// Fall through — applyHudPatch will render its placeholder.
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
433
456
|
// HUD mode — patches modify anchor / HUD-visual fields. World-style
|
|
434
457
|
// transform / sprite-tint patches don't apply to HUD widgets, so we
|
|
435
458
|
// dispatch through a HUD-specific helper that knows the widget kinds.
|
|
@@ -625,6 +648,51 @@ async function createEntity(game, entity, manifestAsset) {
|
|
|
625
648
|
console.warn('[unboxy/editor] spawnEntity failed:', e);
|
|
626
649
|
}
|
|
627
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Merge an asset record into the scene's cached manifest (the one
|
|
653
|
+
* {@code getManifest(scene)} returns). Used by applyEdit's pipelined-asset
|
|
654
|
+
* path so subsequent re-spawns see the updated entry (kind / atlasPath /
|
|
655
|
+
* atlasFormat / ninePatch / etc.) without a full scene reload.
|
|
656
|
+
*
|
|
657
|
+
* <p>The manifest object lives in `scene.cache.json` and is shared by
|
|
658
|
+
* reference — mutating its `assets[]` is sufficient. We replace in place
|
|
659
|
+
* when an entry with the same id exists (so a stale {@code kind: 'image'}
|
|
660
|
+
* gets fully overwritten by a fresh {@code kind: 'atlas'} record).
|
|
661
|
+
*/
|
|
662
|
+
function upsertCachedManifestAsset(scene, asset) {
|
|
663
|
+
let manifest;
|
|
664
|
+
try {
|
|
665
|
+
manifest = getManifest(scene);
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
return; // manifest not in cache — nothing to merge against
|
|
669
|
+
}
|
|
670
|
+
if (!manifest.assets)
|
|
671
|
+
manifest.assets = [];
|
|
672
|
+
const idx = manifest.assets.findIndex((a) => a.id === asset.id);
|
|
673
|
+
if (idx >= 0)
|
|
674
|
+
manifest.assets[idx] = asset;
|
|
675
|
+
else
|
|
676
|
+
manifest.assets.push(asset);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Idempotent asset-loaded check: returns immediately if the texture is in
|
|
680
|
+
* the cache. For atlas-format assets, ALSO ensures the per-frame ninePatch
|
|
681
|
+
* sidecar JSON is loaded so the SDK's region-9-slice path can read it back.
|
|
682
|
+
*/
|
|
683
|
+
function ensureAssetLoaded(scene, asset) {
|
|
684
|
+
if (asset.kind === 'audio')
|
|
685
|
+
return Promise.resolve();
|
|
686
|
+
const needsTexture = !scene.textures.exists(asset.textureKey);
|
|
687
|
+
const sidecarKey = `unboxy:atlas-ninepatch:${asset.textureKey}`;
|
|
688
|
+
const needsSidecar = asset.kind === 'atlas' &&
|
|
689
|
+
asset.atlasFormat === 'json' &&
|
|
690
|
+
!!asset.atlasPath &&
|
|
691
|
+
!scene.cache.json.exists(sidecarKey);
|
|
692
|
+
if (!needsTexture && !needsSidecar)
|
|
693
|
+
return Promise.resolve();
|
|
694
|
+
return loadAssetIntoScene(scene, asset);
|
|
695
|
+
}
|
|
628
696
|
function loadAssetIntoScene(scene, asset) {
|
|
629
697
|
return new Promise((resolve, reject) => {
|
|
630
698
|
if (scene.textures.exists(asset.textureKey)) {
|
|
@@ -673,6 +741,13 @@ function loadAssetIntoScene(scene, asset) {
|
|
|
673
741
|
}
|
|
674
742
|
else if (asset.atlasPath) {
|
|
675
743
|
scene.load.atlas(asset.textureKey, asset.path, asset.atlasPath);
|
|
744
|
+
// Also fetch the raw JSON into the side-cache for per-region
|
|
745
|
+
// ninePatch lookups (slice 10). Phaser's atlas parser drops
|
|
746
|
+
// unknown per-frame fields, so we keep the original JSON around.
|
|
747
|
+
const sidecarKey = `unboxy:atlas-ninepatch:${asset.textureKey}`;
|
|
748
|
+
if (!scene.cache.json.exists(sidecarKey)) {
|
|
749
|
+
scene.load.json(sidecarKey, asset.atlasPath);
|
|
750
|
+
}
|
|
676
751
|
}
|
|
677
752
|
break;
|
|
678
753
|
case 'audio':
|
package/dist/protocol.d.ts
CHANGED
|
@@ -130,6 +130,21 @@ export interface EditorApplyEditMessage {
|
|
|
130
130
|
type: 'unboxy:editor:applyEdit';
|
|
131
131
|
entityId: string;
|
|
132
132
|
patch: EditorEntityPatch;
|
|
133
|
+
/**
|
|
134
|
+
* Optional asset record to upsert into the iframe's manifest cache + lazy-
|
|
135
|
+
* load before the patch applies. Mirrors {@link EditorCreateEntityMessage}'s
|
|
136
|
+
* `manifestAsset` field. Used by the Inspector's Bg-image picker when the
|
|
137
|
+
* user picks an asset whose manifest entry needs flipping (e.g. region
|
|
138
|
+
* atlases — visual editor slice 10 — where the existing manifest entry
|
|
139
|
+
* was {@code kind: 'image'} but the asset is now atlas-format).
|
|
140
|
+
*
|
|
141
|
+
* <p>When present, the bridge:
|
|
142
|
+
* 1. Merges the asset into {@code scene.cache.json}'s manifest record
|
|
143
|
+
* 2. Lazy-loads the texture (and atlas-ninepatch sidecar JSON when
|
|
144
|
+
* {@code kind: 'atlas' + atlasFormat: 'json'})
|
|
145
|
+
* 3. Applies the entity patch
|
|
146
|
+
*/
|
|
147
|
+
manifestAsset?: unknown;
|
|
133
148
|
}
|
|
134
149
|
export interface EditorSetSelectionMessage {
|
|
135
150
|
type: 'unboxy:editor:setSelection';
|
package/dist/scene/HudRuntime.js
CHANGED
|
@@ -2,7 +2,7 @@ import Phaser from 'phaser';
|
|
|
2
2
|
import { SCHEMA_VERSION, isPerFrameNinePatch, } from './types.js';
|
|
3
3
|
import { attachEntityRegistry, getEntityRegistry, } from './EntityRegistry.js';
|
|
4
4
|
import { parseColor } from './spawnEntity.js';
|
|
5
|
-
import { applyPixelArtFilters, getManifest, SCENES_BASE } from './SceneLoader.js';
|
|
5
|
+
import { applyPixelArtFilters, getAtlasFrameNinePatch, getManifest, SCENES_BASE, } from './SceneLoader.js';
|
|
6
6
|
/**
|
|
7
7
|
* HUD runtime — slice 5.
|
|
8
8
|
*
|
|
@@ -99,13 +99,26 @@ const LAYER_DEPTH = {
|
|
|
99
99
|
* added to the scene's display list.
|
|
100
100
|
*/
|
|
101
101
|
function createImageOrNinePatch(scene, x, y, width, height, asset, frame) {
|
|
102
|
+
// Region-atlas path (slice 10) — when the asset is an atlas-format texture
|
|
103
|
+
// AND the caller picks a frame by string name, per-region ninePatch
|
|
104
|
+
// metadata may live in the atlas JSON. Phaser drops unknown per-frame
|
|
105
|
+
// fields on parse, so we read from the side-cache stashed at preload.
|
|
106
|
+
if (asset.kind === 'atlas' && asset.atlasFormat === 'json' && typeof frame === 'string') {
|
|
107
|
+
const cuts = getAtlasFrameNinePatch(scene, asset.textureKey, frame);
|
|
108
|
+
if (cuts) {
|
|
109
|
+
return createCustomNinePatch(scene, x, y, width, height, asset, cuts, frame);
|
|
110
|
+
}
|
|
111
|
+
// Atlas frame without 9-slice metadata → plain stretched Image of that
|
|
112
|
+
// region. Falls through to the generic fallback below.
|
|
113
|
+
}
|
|
102
114
|
if (asset.ninePatch) {
|
|
103
115
|
const np = asset.ninePatch;
|
|
104
116
|
const cuts = isPerFrameNinePatch(np) ? np.perFrame : np;
|
|
105
117
|
return createCustomNinePatch(scene, x, y, width, height, asset, cuts, frame);
|
|
106
118
|
}
|
|
107
|
-
// Fallback: plain Image. Frame index
|
|
108
|
-
// assets so per-cell rendering still works
|
|
119
|
+
// Fallback: plain Image. Frame (number index or string atlas-name) threaded
|
|
120
|
+
// through for spritesheet / atlas assets so per-cell rendering still works
|
|
121
|
+
// without 9-slice.
|
|
109
122
|
const image = frame !== undefined
|
|
110
123
|
? scene.add.image(x, y, asset.textureKey, frame)
|
|
111
124
|
: scene.add.image(x, y, asset.textureKey);
|
|
@@ -139,8 +152,9 @@ function createImageOrNinePatch(scene, x, y, width, height, asset, frame) {
|
|
|
139
152
|
function createCustomNinePatch(scene, x, y, width, height, asset, cuts, frame) {
|
|
140
153
|
const texKey = asset.textureKey;
|
|
141
154
|
const texture = scene.textures.get(texKey);
|
|
142
|
-
// Per-frame variant: look up the specified cell's source rect
|
|
143
|
-
//
|
|
155
|
+
// Per-frame variant: look up the specified cell's source rect (number for
|
|
156
|
+
// uniform-grid sheets, atlas-name string for region atlases). Single image
|
|
157
|
+
// uses the texture's `__BASE` frame covering the whole image.
|
|
144
158
|
const baseFrame = frame !== undefined
|
|
145
159
|
? texture.get(frame)
|
|
146
160
|
: texture.get('__BASE');
|
|
@@ -373,7 +387,10 @@ function createIconButton(ctx, entity, pos) {
|
|
|
373
387
|
if (v.backgroundAssetId) {
|
|
374
388
|
try {
|
|
375
389
|
const bgAsset = ctx.resolveAsset(v.backgroundAssetId);
|
|
376
|
-
|
|
390
|
+
// Region-atlas name (string) wins when both are set; uniform-grid index
|
|
391
|
+
// (number) is the fallback for slice 7.6 button packs.
|
|
392
|
+
const bgFrame = v.backgroundRegion ?? v.backgroundFrame;
|
|
393
|
+
const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset, bgFrame);
|
|
377
394
|
np.setOrigin?.(0.5, 0.5);
|
|
378
395
|
bg = np;
|
|
379
396
|
texturedBg = true;
|
|
@@ -551,7 +568,8 @@ function createPanel(ctx, entity, pos) {
|
|
|
551
568
|
if (v.backgroundAssetId) {
|
|
552
569
|
try {
|
|
553
570
|
const bgAsset = ctx.resolveAsset(v.backgroundAssetId);
|
|
554
|
-
const
|
|
571
|
+
const bgFrame = v.backgroundRegion ?? v.backgroundFrame;
|
|
572
|
+
const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset, bgFrame);
|
|
555
573
|
np.setOrigin?.(0.5, 0.5);
|
|
556
574
|
container.add(np);
|
|
557
575
|
bgRendered = true;
|
|
@@ -732,6 +750,13 @@ export function preloadHudAssets(scene, hudScene, manifest) {
|
|
|
732
750
|
}
|
|
733
751
|
else if (asset.atlasPath && asset.atlasFormat === 'json') {
|
|
734
752
|
scene.load.atlas(asset.textureKey, asset.path, asset.atlasPath);
|
|
753
|
+
// Also fetch the raw JSON into the side-cache so per-frame
|
|
754
|
+
// `ninePatch` metadata (which Phaser drops on parse) is readable at
|
|
755
|
+
// spawn time via `getAtlasFrameNinePatch`. See SceneLoader.ts.
|
|
756
|
+
const sidecarKey = `unboxy:atlas-ninepatch:${asset.textureKey}`;
|
|
757
|
+
if (!scene.cache.json.exists(sidecarKey)) {
|
|
758
|
+
scene.load.json(sidecarKey, asset.atlasPath);
|
|
759
|
+
}
|
|
735
760
|
}
|
|
736
761
|
}
|
|
737
762
|
}
|
|
@@ -60,6 +60,18 @@ export declare function preloadSceneAssets(scene: Phaser.Scene, sceneFile: Scene
|
|
|
60
60
|
* concern (framebuffer-stretch crispness, perf) deferred to a follow-up.
|
|
61
61
|
*/
|
|
62
62
|
export declare function applyPixelArtFilters(scene: Phaser.Scene, manifest: Manifest): void;
|
|
63
|
+
/**
|
|
64
|
+
* Look up per-frame 9-slice metadata for an atlas-format asset. Returns the
|
|
65
|
+
* `NinePatchConfig` declared inline on the named frame in the atlas JSON, or
|
|
66
|
+
* `undefined` if the asset isn't an atlas, the cache miss happened, or the
|
|
67
|
+
* frame has no `ninePatch` entry (plain stretched draw).
|
|
68
|
+
*/
|
|
69
|
+
export declare function getAtlasFrameNinePatch(scene: Phaser.Scene, textureKey: string, frameName: string): {
|
|
70
|
+
leftWidth: number;
|
|
71
|
+
rightWidth: number;
|
|
72
|
+
topHeight: number;
|
|
73
|
+
bottomHeight: number;
|
|
74
|
+
} | undefined;
|
|
63
75
|
export interface LoadWorldSceneOptions {
|
|
64
76
|
/**
|
|
65
77
|
* Optional render-script resolver for `code-rendered` entities. When
|
|
@@ -196,6 +196,55 @@ function registerSheetAnimations(scene, manifest) {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Atlas-json side-cache key. Phaser's `scene.load.atlas()` loads + parses the
|
|
201
|
+
* JSON but drops unknown per-frame fields (e.g. `ninePatch`) — they don't
|
|
202
|
+
* survive into the Phaser Frame object. To consume them at runtime we ALSO
|
|
203
|
+
* fetch the raw JSON via `scene.load.json()` and stash it under this key so
|
|
204
|
+
* `getAtlasFrameNinePatch` can read it back.
|
|
205
|
+
*
|
|
206
|
+
* Used by region-atlas assets (visual editor slice 10) where each tagged
|
|
207
|
+
* region can carry its own 9-slice cuts.
|
|
208
|
+
*/
|
|
209
|
+
function atlasNinePatchCacheKey(textureKey) {
|
|
210
|
+
return `unboxy:atlas-ninepatch:${textureKey}`;
|
|
211
|
+
}
|
|
212
|
+
function queueAtlasNinePatchSidecar(scene, asset) {
|
|
213
|
+
if (!asset.atlasPath)
|
|
214
|
+
return;
|
|
215
|
+
const cacheKey = atlasNinePatchCacheKey(asset.textureKey);
|
|
216
|
+
if (scene.cache.json.exists(cacheKey))
|
|
217
|
+
return;
|
|
218
|
+
scene.load.json(cacheKey, asset.atlasPath);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Look up per-frame 9-slice metadata for an atlas-format asset. Returns the
|
|
222
|
+
* `NinePatchConfig` declared inline on the named frame in the atlas JSON, or
|
|
223
|
+
* `undefined` if the asset isn't an atlas, the cache miss happened, or the
|
|
224
|
+
* frame has no `ninePatch` entry (plain stretched draw).
|
|
225
|
+
*/
|
|
226
|
+
export function getAtlasFrameNinePatch(scene, textureKey, frameName) {
|
|
227
|
+
const cacheKey = atlasNinePatchCacheKey(textureKey);
|
|
228
|
+
if (!scene.cache.json.exists(cacheKey))
|
|
229
|
+
return undefined;
|
|
230
|
+
const raw = scene.cache.json.get(cacheKey);
|
|
231
|
+
const entry = raw?.frames?.[frameName];
|
|
232
|
+
const np = entry?.ninePatch;
|
|
233
|
+
if (!np)
|
|
234
|
+
return undefined;
|
|
235
|
+
if (typeof np.leftWidth !== 'number' ||
|
|
236
|
+
typeof np.rightWidth !== 'number' ||
|
|
237
|
+
typeof np.topHeight !== 'number' ||
|
|
238
|
+
typeof np.bottomHeight !== 'number') {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
leftWidth: np.leftWidth,
|
|
243
|
+
rightWidth: np.rightWidth,
|
|
244
|
+
topHeight: np.topHeight,
|
|
245
|
+
bottomHeight: np.bottomHeight,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
199
248
|
function queueAssetLoad(scene, asset) {
|
|
200
249
|
if (scene.textures.exists(asset.textureKey))
|
|
201
250
|
return;
|
|
@@ -235,6 +284,7 @@ function queueAssetLoad(scene, asset) {
|
|
|
235
284
|
}
|
|
236
285
|
else {
|
|
237
286
|
scene.load.atlas(asset.textureKey, asset.path, asset.atlasPath);
|
|
287
|
+
queueAtlasNinePatchSidecar(scene, asset);
|
|
238
288
|
}
|
|
239
289
|
return;
|
|
240
290
|
case 'audio':
|
package/dist/scene/types.d.ts
CHANGED
|
@@ -408,9 +408,18 @@ export interface HudIconButtonVisual {
|
|
|
408
408
|
/**
|
|
409
409
|
* For uniform-grid spritesheet backgrounds (slice 7.6): index of the cell
|
|
410
410
|
* to render. Defaults to 0 when omitted. Ignored when the background asset
|
|
411
|
-
* is a single image.
|
|
411
|
+
* is a single image. Mutually exclusive with `backgroundRegion` — when both
|
|
412
|
+
* are set, the SDK picks `backgroundRegion` (atlas-name lookup wins).
|
|
412
413
|
*/
|
|
413
414
|
backgroundFrame?: number;
|
|
415
|
+
/**
|
|
416
|
+
* For region-atlas backgrounds (slice 10): name of the atlas frame to
|
|
417
|
+
* render. Used when the referenced asset is `kind: 'atlas'` + `atlasFormat:
|
|
418
|
+
* 'json'` — the region atlas can carry per-frame `ninePatch` metadata, in
|
|
419
|
+
* which case the SDK 9-slices each region independently. Mutually exclusive
|
|
420
|
+
* with `backgroundFrame`.
|
|
421
|
+
*/
|
|
422
|
+
backgroundRegion?: string;
|
|
414
423
|
/** Button shape. Default `rounded-rect`. Ignored when backgroundAssetId is set. */
|
|
415
424
|
shape?: 'rounded-rect' | 'circle';
|
|
416
425
|
width?: number;
|
|
@@ -471,9 +480,14 @@ export interface HudPanelVisual {
|
|
|
471
480
|
/**
|
|
472
481
|
* For uniform-grid spritesheet backgrounds (slice 7.6): index of the cell
|
|
473
482
|
* to render. Defaults to 0 when omitted. Ignored when the background asset
|
|
474
|
-
* is a single image.
|
|
483
|
+
* is a single image. Mutually exclusive with `backgroundRegion`.
|
|
475
484
|
*/
|
|
476
485
|
backgroundFrame?: number;
|
|
486
|
+
/**
|
|
487
|
+
* For region-atlas backgrounds (slice 10): name of the atlas frame to
|
|
488
|
+
* render. See `HudIconButtonVisual.backgroundRegion` for semantics.
|
|
489
|
+
*/
|
|
490
|
+
backgroundRegion?: string;
|
|
477
491
|
/** Solid fill colour. Falls back to a transparent panel if omitted. */
|
|
478
492
|
backgroundColor?: string;
|
|
479
493
|
/** Background alpha (independent of widget-level alpha). */
|