@unboxy/phaser-sdk 0.2.40 → 0.2.42

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.
@@ -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 threaded through for spritesheet
108
- // assets so per-cell rendering still works without 9-slice.
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. Single
143
- // image: the texture's `__BASE` frame covers the entire image.
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
- const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset, v.backgroundFrame);
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 np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset, v.backgroundFrame);
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
  }
@@ -1093,25 +1118,19 @@ export function applyHudPatch(game, entityId, patch) {
1093
1118
  }
1094
1119
  }
1095
1120
  if (patch.visual && entity.kind === 'icon-button') {
1096
- // Icon-button is a Container — visual patches require a small refresh.
1097
- // For v1 we just patch the entity record + label/colour fields cheaply;
1098
- // wholesale re-spawn lands in slice 5.5 if we add live re-styling.
1121
+ // Icon-button is a Container — visual patches re-spawn the container
1122
+ // in place. Use a generic loop to copy every patched field into the
1123
+ // entity record so we don't have to enumerate fields here every time
1124
+ // the schema grows. Older fields kept as explicit for clarity below
1125
+ // are now subsumed by this loop but left commented as a reminder.
1099
1126
  const v = entity.visual;
1100
1127
  const p = patch.visual;
1101
- if (typeof p.label === 'string')
1102
- v.label = p.label;
1103
- if (typeof p.fillColor === 'string')
1104
- v.fillColor = p.fillColor;
1105
- if (typeof p.iconAssetId === 'string')
1106
- v.iconAssetId = p.iconAssetId;
1107
- if (typeof p.backgroundAssetId === 'string')
1108
- v.backgroundAssetId = p.backgroundAssetId;
1109
- else if (p.backgroundAssetId === null)
1110
- v.backgroundAssetId = undefined;
1111
- if (typeof p.backgroundFrame === 'number')
1112
- v.backgroundFrame = p.backgroundFrame;
1113
- else if (p.backgroundFrame === null)
1114
- v.backgroundFrame = undefined;
1128
+ for (const [k, val] of Object.entries(p)) {
1129
+ if (val === null)
1130
+ delete v[k];
1131
+ else if (val !== undefined)
1132
+ v[k] = val;
1133
+ }
1115
1134
  // Patches that change the rendered visual deeply (label, colour, icon)
1116
1135
  // re-render by destroying + re-spawning the container in place.
1117
1136
  const reg2 = reg;
@@ -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':
@@ -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). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboxy/phaser-sdk",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Unboxy Phaser 3 SDK — game infrastructure for the Unboxy platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",