@unboxy/phaser-sdk 0.2.37 → 0.2.39

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/index.d.ts CHANGED
@@ -27,6 +27,6 @@ export type { RenderScriptModule } from './scene/renderScripts.js';
27
27
  export { setupEditorBridge } from './editor/EditorBridge.js';
28
28
  export { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './editor/EditorOverlayScene.js';
29
29
  export type { EditorEntityPatch, EditorEnterMessage, EditorExitMessage, EditorGetSceneMessage, EditorApplyEditMessage, EditorSetSelectionMessage, EditorPanZoomMessage, EditorHostToSdkMessage, EditorSceneLoadedMessage, EditorSelectionPickedMessage, EditorDragEndMessage, EditorShortcutMessage, EditorCreateEntityMessage, EditorDeleteEntityMessage, EditorSetEditModeMessage, EditorSelectionRectMessage, EditorSdkToHostMessage, } from './protocol.js';
30
- export { SCHEMA_VERSION, } from './scene/types.js';
31
- export type { Manifest, SceneRef, HudRef, AssetRecord, AssetKind, SceneType, SceneFile, WorldScene, HudScene, WorldSceneConfig, CameraConfig, WorldEntity, WorldEntityKind, NonGroupWorldEntity, SpriteEntity, PrimitiveEntity, CodeRenderedEntity, GroupEntity, TilemapEntity, TriggerEntity, TriggerShape, WorldVisual, SpriteVisual, PrimitiveVisual, PrimitiveRectVisual, PrimitiveCircleVisual, CodeRenderedVisual, HudEntity, HudEntityKind, HudEntityBase, HudTextEntity, HudImageEntity, HudIconButtonEntity, HudProgressBarEntity, HudPanelEntity, HudVisual, HudTextVisual, HudImageVisual, HudIconButtonVisual, HudProgressBarVisual, HudPanelVisual, HudTextSource, HudNumberSource, HudLayer, Transform, Anchor, AnchorSide, NinePatchConfig, } from './scene/types.js';
30
+ export { SCHEMA_VERSION, isPerFrameNinePatch, } from './scene/types.js';
31
+ export type { Manifest, SceneRef, HudRef, AssetRecord, AssetKind, SceneType, SceneFile, WorldScene, HudScene, WorldSceneConfig, CameraConfig, WorldEntity, WorldEntityKind, NonGroupWorldEntity, SpriteEntity, PrimitiveEntity, CodeRenderedEntity, GroupEntity, TilemapEntity, TriggerEntity, TriggerShape, WorldVisual, SpriteVisual, PrimitiveVisual, PrimitiveRectVisual, PrimitiveCircleVisual, CodeRenderedVisual, HudEntity, HudEntityKind, HudEntityBase, HudTextEntity, HudImageEntity, HudIconButtonEntity, HudProgressBarEntity, HudPanelEntity, HudVisual, HudTextVisual, HudImageVisual, HudIconButtonVisual, HudProgressBarVisual, HudPanelVisual, HudTextSource, HudNumberSource, HudLayer, Transform, Anchor, AnchorSide, NinePatchConfig, NinePatchPerFrame, } from './scene/types.js';
32
32
  export { PROTOCOL_VERSION, type HelloMessage, type InitMessage, type RpcRequestMessage, type RpcResultOk, type RpcResultError, type HostToSdkMessage, type SdkToHostMessage, type RpcErrorPayload, type RpcMethod, type SavesGetParams, type SavesGetResult, type SavesSetParams, type SavesSetResult, type SavesDeleteParams, type SavesDeleteResult, type SavesListResult, type GameDataGetParams, type GameDataGetResult, type GameDataSetParams, type GameDataSetResult, type GameDataDeleteParams, type GameDataDeleteResult, type GameDataListResult, type RealtimeGetTokenParams, type RealtimeGetTokenResult, } from './protocol.js';
package/dist/index.js CHANGED
@@ -21,5 +21,5 @@ export { setupEditorModeListener, isEditMode } from './scene/EditorMode.js';
21
21
  export { setRenderScriptRegistry, getRenderScriptRegistry, resolveRenderScript, } from './scene/renderScripts.js';
22
22
  export { setupEditorBridge } from './editor/EditorBridge.js';
23
23
  export { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './editor/EditorOverlayScene.js';
24
- export { SCHEMA_VERSION, } from './scene/types.js';
24
+ export { SCHEMA_VERSION, isPerFrameNinePatch, } from './scene/types.js';
25
25
  export { PROTOCOL_VERSION, } from './protocol.js';
@@ -1,5 +1,5 @@
1
1
  import Phaser from 'phaser';
2
- import { SCHEMA_VERSION, } from './types.js';
2
+ import { SCHEMA_VERSION, isPerFrameNinePatch, } from './types.js';
3
3
  import { attachEntityRegistry, getEntityRegistry, } from './EntityRegistry.js';
4
4
  import { parseColor } from './spawnEntity.js';
5
5
  import { applyPixelArtFilters, getManifest, SCENES_BASE } from './SceneLoader.js';
@@ -80,21 +80,51 @@ const LAYER_DEPTH = {
80
80
  modal: 300,
81
81
  };
82
82
  /**
83
- * Render an image asset at (x, y) sized to (width, height). When the asset
84
- * carries `ninePatch` metadata, uses `phaser3-rex-plugins`' NinePatch (corners
85
- * fixed, edges + center stretch); otherwise falls back to a regular Image
86
- * scaled with `setDisplaySize`. Caller owns origin/anchor both Image and
87
- * NinePatch (which extends RenderTexture) expose `setOrigin`. Returns the
88
- * created GameObject already added to the scene's display list.
83
+ * Render an image (or one cell of a uniform-grid spritesheet) at (x, y) sized
84
+ * to (width, height). When the asset carries `ninePatch` metadata, uses
85
+ * `phaser3-rex-plugins`' NinePatch (corners fixed, edges + center stretch);
86
+ * otherwise falls back to a regular Image scaled with `setDisplaySize`.
87
+ *
88
+ * Two ninePatch shapes accepted:
89
+ * - Single image (slice 7.5): `asset.ninePatch` is a `NinePatchConfig`. The
90
+ * whole texture slices into 9 regions.
91
+ * - Per-frame on a uniform-grid sheet (slice 7.6): `asset.ninePatch` is
92
+ * `{ perFrame: NinePatchConfig }`. The shared cuts apply to whichever
93
+ * frame the caller picks via `frame`. Common case: itch.io button packs
94
+ * where every cell is a different button color/state with the same
95
+ * corner radius.
96
+ *
97
+ * Caller owns origin/anchor — both Image and NinePatch (which extends
98
+ * RenderTexture) expose `setOrigin`. Returns the created GameObject already
99
+ * added to the scene's display list.
89
100
  */
90
- function createImageOrNinePatch(scene, x, y, width, height, asset) {
101
+ function createImageOrNinePatch(scene, x, y, width, height, asset, frame) {
91
102
  if (asset.ninePatch) {
92
103
  const np = asset.ninePatch;
104
+ const isPerFrame = isPerFrameNinePatch(np);
105
+ const cuts = isPerFrame ? np.perFrame : np;
93
106
  const factory = scene.add.rexNinePatch;
94
107
  if (factory) {
95
- // Middle column/row left undefined → that segment stretches to fill
96
- // the remaining space between the two fixed-width edges.
97
- return factory.call(scene.add, x, y, width, height, asset.textureKey, [np.leftWidth, undefined, np.rightWidth], [np.topHeight, undefined, np.bottomHeight]);
108
+ const columns = [cuts.leftWidth, undefined, cuts.rightWidth];
109
+ const rows = [cuts.topHeight, undefined, cuts.bottomHeight];
110
+ // Construct at a placeholder 1×1 size, then resize() to target. Reason:
111
+ // Phaser 3.60+ refactored RenderTexture so that setSize() (which rex's
112
+ // constructor calls) only updates the gameobject's display dimensions,
113
+ // NOT the underlying DynamicTexture's canvas. The 9-slice draw pass
114
+ // then draws into the default 32×32 canvas and only the top-left
115
+ // corner of the patch is visible at 200×64 display. resize() — unlike
116
+ // setSize() — does call DynamicTexture.setSize, but rex's resize early-
117
+ // returns when current dims match target. Forcing initial dims of 1×1
118
+ // ensures resize() to (width, height) does real work.
119
+ let np;
120
+ if (isPerFrame) {
121
+ np = factory.call(scene.add, x, y, 1, 1, asset.textureKey, String(frame ?? 0), columns, rows);
122
+ }
123
+ else {
124
+ np = factory.call(scene.add, x, y, 1, 1, asset.textureKey, columns, rows);
125
+ }
126
+ np.resize(width, height);
127
+ return np;
98
128
  }
99
129
  // Plugin not registered — degrade to a stretched Image rather than crash
100
130
  // an entire HUD render. Symptom is "corners distort on resize", not a
@@ -103,7 +133,11 @@ function createImageOrNinePatch(scene, x, y, width, height, asset) {
103
133
  // eslint-disable-next-line no-console
104
134
  console.warn(`[unboxy/hud] rexNinePatch plugin not registered; falling back to stretched Image for asset '${asset.id}'`);
105
135
  }
106
- const image = scene.add.image(x, y, asset.textureKey);
136
+ // Fallback: plain Image. Frame index threaded through for spritesheet
137
+ // assets so per-cell rendering still works without 9-slice.
138
+ const image = frame !== undefined
139
+ ? scene.add.image(x, y, asset.textureKey, frame)
140
+ : scene.add.image(x, y, asset.textureKey);
107
141
  image.setDisplaySize(width, height);
108
142
  return image;
109
143
  }
@@ -261,7 +295,7 @@ function createIconButton(ctx, entity, pos) {
261
295
  if (v.backgroundAssetId) {
262
296
  try {
263
297
  const bgAsset = ctx.resolveAsset(v.backgroundAssetId);
264
- const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset);
298
+ const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset, v.backgroundFrame);
265
299
  np.setOrigin?.(0.5, 0.5);
266
300
  bg = np;
267
301
  texturedBg = true;
@@ -439,7 +473,7 @@ function createPanel(ctx, entity, pos) {
439
473
  if (v.backgroundAssetId) {
440
474
  try {
441
475
  const bgAsset = ctx.resolveAsset(v.backgroundAssetId);
442
- const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset);
476
+ const np = createImageOrNinePatch(ctx.scene, 0, 0, w, h, bgAsset, v.backgroundFrame);
443
477
  np.setOrigin?.(0.5, 0.5);
444
478
  container.add(np);
445
479
  bgRendered = true;
@@ -996,6 +1030,10 @@ export function applyHudPatch(game, entityId, patch) {
996
1030
  v.backgroundAssetId = p.backgroundAssetId;
997
1031
  else if (p.backgroundAssetId === null)
998
1032
  v.backgroundAssetId = undefined;
1033
+ if (typeof p.backgroundFrame === 'number')
1034
+ v.backgroundFrame = p.backgroundFrame;
1035
+ else if (p.backgroundFrame === null)
1036
+ v.backgroundFrame = undefined;
999
1037
  // Patches that change the rendered visual deeply (label, colour, icon)
1000
1038
  // re-render by destroying + re-spawning the container in place.
1001
1039
  const reg2 = reg;
@@ -73,18 +73,22 @@ export interface AssetRecord {
73
73
  /** Default playback rate for `animations[]`. Defaults to 8. */
74
74
  fps?: number;
75
75
  /**
76
- * For `image`: 9-slice cut-line metadata. When present, HUD widgets that
77
- * render this asset (image / icon-button background / panel background)
78
- * scale via `phaser3-rex-plugins` NinePatch instead of plain stretched
79
- * Image — corners stay fixed, edges + center stretch. Pack-import vision
80
- * captures these on UI button/panel imports; users can edit in the Adjust
81
- * modal at import time.
76
+ * 9-slice cut-line metadata. Two shapes accepted:
82
77
  *
83
- * <p>Single-image only in v1 (slice 7.5). Per-frame 9-slice on uniform-grid
84
- * sheets (e.g. `Square Buttons 26x26.png`) is v2 see
85
- * `unboxy-design/features/visual-editor/07-asset-pack-import.md` §7.9.
78
+ * - **Single image** (slice 7.5) — `NinePatchConfig` directly. Used on
79
+ * `kind: 'image'` assets like a single button/panel background. All four
80
+ * corner widths apply to the image as a whole.
81
+ * - **Per-frame on a uniform grid** (slice 7.6) — `{ perFrame: NinePatchConfig }`.
82
+ * Used on `kind: 'spritesheet'` assets where every cell is its own
83
+ * stretchable button/panel sharing the same corner radius (typical
84
+ * itch.io UI button packs, e.g. `Square Buttons 26x26.png`). The HUD
85
+ * widget references which cell to render via `backgroundFrame`.
86
+ *
87
+ * Per-cell keyed config (`{ perFrame: Record<number, NinePatchConfig> }`)
88
+ * is deferred to v3 per design 07 §7.9.5 — promote only when a real pack
89
+ * needs different corner radii on different cells.
86
90
  */
87
- ninePatch?: NinePatchConfig;
91
+ ninePatch?: NinePatchConfig | NinePatchPerFrame;
88
92
  /**
89
93
  * Vision-detected pixel-art flag. When true, SDK applies
90
94
  * `Phaser.Textures.FilterMode.NEAREST` to the texture after load so it
@@ -101,6 +105,20 @@ export interface NinePatchConfig {
101
105
  topHeight: number;
102
106
  bottomHeight: number;
103
107
  }
108
+ /**
109
+ * Per-frame 9-slice config (slice 7.6). One shared `NinePatchConfig` applied
110
+ * to every frame of a uniform-grid spritesheet. Covers ~95% of itch.io UI
111
+ * button packs, where each cell is a different button color/state but all
112
+ * share the same corner radius.
113
+ */
114
+ export interface NinePatchPerFrame {
115
+ perFrame: NinePatchConfig;
116
+ }
117
+ /**
118
+ * Type guard — true when `ninePatch` is the per-frame variant. Lets call
119
+ * sites that only need to handle one shape stay narrow.
120
+ */
121
+ export declare function isPerFrameNinePatch(np: NinePatchConfig | NinePatchPerFrame | undefined): np is NinePatchPerFrame;
104
122
  export interface SheetAnimation {
105
123
  /** Phaser animation key — used as `sprite.play('<name>')`. */
106
124
  name: string;
@@ -380,8 +398,19 @@ export interface HudIconButtonVisual {
380
398
  * otherwise the image is stretched to the widget's size. When set, the
381
399
  * colored-rect bg below (fillColor / strokeColor / shape) is ignored, and
382
400
  * `pressedFillColor` no-ops (no fill swap on a textured bg in v1).
401
+ *
402
+ * For uniform-grid spritesheet assets with `ninePatch.perFrame` set
403
+ * (slice 7.6), pair this with `backgroundFrame` to pick which cell to
404
+ * render — common case is a button-pack sheet where every cell is a
405
+ * different button color/state.
383
406
  */
384
407
  backgroundAssetId?: string;
408
+ /**
409
+ * For uniform-grid spritesheet backgrounds (slice 7.6): index of the cell
410
+ * to render. Defaults to 0 when omitted. Ignored when the background asset
411
+ * is a single image.
412
+ */
413
+ backgroundFrame?: number;
385
414
  /** Button shape. Default `rounded-rect`. Ignored when backgroundAssetId is set. */
386
415
  shape?: 'rounded-rect' | 'circle';
387
416
  width?: number;
@@ -433,8 +462,18 @@ export interface HudPanelVisual {
433
462
  * otherwise the image is stretched to the widget's size. When set,
434
463
  * `backgroundColor` / `backgroundAlpha` / `borderColor` / `borderWidth` are
435
464
  * ignored — the asset image carries its own pixels.
465
+ *
466
+ * For uniform-grid spritesheet assets with `ninePatch.perFrame` set
467
+ * (slice 7.6), pair this with `backgroundFrame` to pick which cell to
468
+ * render.
436
469
  */
437
470
  backgroundAssetId?: string;
471
+ /**
472
+ * For uniform-grid spritesheet backgrounds (slice 7.6): index of the cell
473
+ * to render. Defaults to 0 when omitted. Ignored when the background asset
474
+ * is a single image.
475
+ */
476
+ backgroundFrame?: number;
438
477
  /** Solid fill colour. Falls back to a transparent panel if omitted. */
439
478
  backgroundColor?: string;
440
479
  /** Background alpha (independent of widget-level alpha). */
@@ -8,3 +8,10 @@
8
8
  * code-rendered visuals will land in later slices.
9
9
  */
10
10
  export const SCHEMA_VERSION = 1;
11
+ /**
12
+ * Type guard — true when `ninePatch` is the per-frame variant. Lets call
13
+ * sites that only need to handle one shape stay narrow.
14
+ */
15
+ export function isPerFrameNinePatch(np) {
16
+ return !!np && 'perFrame' in np;
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboxy/phaser-sdk",
3
- "version": "0.2.37",
3
+ "version": "0.2.39",
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",