@unboxy/phaser-sdk 0.2.32 → 0.2.33

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.
Files changed (2) hide show
  1. package/SDK-GUIDE.md +186 -0
  2. package/package.json +1 -1
package/SDK-GUIDE.md CHANGED
@@ -626,6 +626,181 @@ The `scene-data-architecture` agent skill has the full guidance. The `scene-data
626
626
 
627
627
  **Edit mode (host-driven):** when the host (home-ui) sends `{ type: 'unboxy:setEditMode', enabled: true }` via `postMessage`, the SDK pauses every active non-Boot Phaser scene — entities stay rendered but `update`/physics/tweens stop. Sending `enabled: false` resumes. Used by the visual editor's read-only viewer in slice 1.
628
628
 
629
+ ## HUD scenes (visual editor slice 5+, since 0.2.29)
630
+
631
+ A HUD scene is a separate `Phaser.Scene` that runs in parallel with the active world scene and renders anchor-positioned UI on top of it. The visual editor's `HUD` mode edits these. Five widget kinds ship today: **text**, **image**, **icon-button**, **progress-bar**, **panel**.
632
+
633
+ ### Manifest reference
634
+
635
+ ```json
636
+ {
637
+ "scenes": [
638
+ { "id": "main", "type": "world", "file": "world/main.json", "hud": "game-hud" }
639
+ ],
640
+ "huds": [
641
+ { "id": "game-hud", "file": "hud/game-hud.json" }
642
+ ]
643
+ }
644
+ ```
645
+
646
+ When `loadWorldScene(this, sceneId)` runs, it auto-launches the `UnboxyHudScene` with the matching `hudId`. You don't call `loadHudScene` yourself — `loadWorldScene` does it for you. Multiple world scenes can reference the same HUD; editing `game-hud` updates every scene that points at it.
647
+
648
+ ### Scene file shape
649
+
650
+ `public/scenes/hud/game-hud.json`:
651
+
652
+ ```json
653
+ {
654
+ "schemaVersion": 1,
655
+ "id": "game-hud",
656
+ "name": "Game HUD",
657
+ "type": "hud",
658
+ "design": {
659
+ "designAspectRatio": "16:9",
660
+ "safeArea": { "top": 16, "right": 16, "bottom": 16, "left": 16 }
661
+ },
662
+ "entities": [ /* HudEntity[] */ ]
663
+ }
664
+ ```
665
+
666
+ ### Anchor + offset positioning (no transform)
667
+
668
+ HUD entities use a **9-grid anchor + numeric offset** instead of world coords:
669
+
670
+ ```json
671
+ {
672
+ "id": "score-text",
673
+ "kind": "text",
674
+ "anchor": { "side": "top-left", "offsetX": 16, "offsetY": 16 },
675
+ "layer": "base",
676
+ "visual": { "kind": "text", "source": { "mode": "static", "text": "Score: 0" } }
677
+ }
678
+ ```
679
+
680
+ `side` is one of `top-left | top | top-right | left | center | right | bottom-left | bottom | bottom-right`. The widget is anchored to that corner/edge of the canvas, then offset inward (or outward) by `offsetX/Y`. Resizing the canvas (orientation change, device rotation) re-resolves anchors automatically.
681
+
682
+ `layer` is one of `base | overlay | modal` (default `base`) — controls render z-order; `modal` sits on top of `overlay` sits on top of `base`. `z` adds further ordering within a layer.
683
+
684
+ ### Widget reference
685
+
686
+ #### `text`
687
+
688
+ ```json
689
+ {
690
+ "kind": "text",
691
+ "anchor": { "side": "top-left", "offsetX": 16, "offsetY": 16 },
692
+ "visual": {
693
+ "kind": "text",
694
+ "source": { "mode": "static", "text": "Hello" },
695
+ "fontSize": 24, "color": "#ffffff", "align": "left"
696
+ }
697
+ }
698
+ ```
699
+
700
+ `source` is either `{ mode: "static", text }` for a literal string OR `{ mode: "dynamic", binding, prefix?, suffix?, fallback? }` for a value read live from Phaser's game registry (see "Dynamic bindings" below).
701
+
702
+ #### `image`
703
+
704
+ ```json
705
+ {
706
+ "kind": "image",
707
+ "anchor": { "side": "top-right", "offsetX": -16, "offsetY": 16 },
708
+ "visual": { "kind": "image", "assetId": "coin-icon", "width": 32, "height": 32 }
709
+ }
710
+ ```
711
+
712
+ `assetId` resolves through `manifest.assets` exactly like world sprite entities. Optional `tint`, `alpha`, `frame` (atlas frame name or sprite-sheet index).
713
+
714
+ #### `icon-button`
715
+
716
+ ```json
717
+ {
718
+ "kind": "icon-button",
719
+ "anchor": { "side": "bottom-right", "offsetX": -32, "offsetY": -32 },
720
+ "layer": "overlay",
721
+ "visual": {
722
+ "kind": "icon-button",
723
+ "label": "Pause", "shape": "rounded-rect",
724
+ "width": 96, "height": 48,
725
+ "fillColor": "#3b82f6", "textColor": "#ffffff",
726
+ "iconAssetId": null
727
+ }
728
+ }
729
+ ```
730
+
731
+ Click handler is decoupled — the SDK emits a Phaser scene event `'hud:press'` with the entity id when the button is pressed. Subscribe in your gameplay code:
732
+
733
+ ```ts
734
+ // In GameScene.ts (or any active scene)
735
+ this.events.on('hud:press', (id: string) => {
736
+ if (id === 'pause-button') { this.scene.pause(); /* etc. */ }
737
+ });
738
+ ```
739
+
740
+ For now there's no built-in action binding (no `behavior: { type: 'pause-game' }` etc.) — you wire each button by its id explicitly. That's coming in a later slice.
741
+
742
+ #### `progress-bar`
743
+
744
+ ```json
745
+ {
746
+ "kind": "progress-bar",
747
+ "anchor": { "side": "top-left", "offsetX": 16, "offsetY": 50 },
748
+ "visual": {
749
+ "kind": "progress-bar",
750
+ "value": { "mode": "dynamic", "binding": "playerHp", "fallback": 0 },
751
+ "max": { "mode": "dynamic", "binding": "playerMaxHp", "fallback": 100 },
752
+ "width": 200, "height": 16,
753
+ "fillColor": "#22c55e", "backgroundColor": "#0f172a",
754
+ "borderRadius": 4, "borderColor": "#ffffff", "borderWidth": 1
755
+ }
756
+ }
757
+ ```
758
+
759
+ `value` and `max` are independently bindable — either can be static (`{ mode: 'static', value: 100 }`) or dynamic. Bar fills from 0 → 1 as `value / max`. Subscribes to `changedata-<binding>` registry events and redraws on change.
760
+
761
+ #### `panel`
762
+
763
+ ```json
764
+ {
765
+ "kind": "panel",
766
+ "anchor": { "side": "center" },
767
+ "visual": {
768
+ "kind": "panel",
769
+ "width": 320, "height": 200,
770
+ "backgroundColor": "#000000", "backgroundAlpha": 0.5,
771
+ "borderColor": "#ffffff", "borderWidth": 1, "borderRadius": 8
772
+ }
773
+ }
774
+ ```
775
+
776
+ A colored rectangle with optional border + rounded corners. Use as a visual frame behind groups of HUD widgets (no children layout in v1 — child widgets are sibling entities; you align them with anchor + offset).
777
+
778
+ ### Dynamic bindings — driving the HUD from gameplay
779
+
780
+ For values that change during play (score, HP, timer, lives), use **Phaser's game registry** as the bridge. The HUD widget references a `binding` key; gameplay code calls `this.registry.set(key, value)` and the HUD auto-refreshes.
781
+
782
+ ```ts
783
+ // In GameScene.ts create():
784
+ this.registry.set('score', 0);
785
+ this.registry.set('playerHp', 100);
786
+ this.registry.set('playerMaxHp', 100);
787
+
788
+ // Wherever score changes:
789
+ this.registry.set('score', this.score + 10);
790
+ ```
791
+
792
+ The HUD widget's text / progress fill updates synchronously on the next frame — no manual `setText` / re-render call needed. Registry is `game.registry` under the hood, shared across all scenes.
793
+
794
+ **Pick stable, lowercase keys**: `score`, `coins`, `playerHp`, `lives`, `currentLevel`, `xp`. The binding key goes into a `changedata-<key>` event name; spaces or special characters break it.
795
+
796
+ **No declare ceremony**: just `set` the key. The HUD picks it up. The `unboxy.gameData` module is for *persistent* shared values (across sessions); for in-game reactive HUD, registry is the right tool.
797
+
798
+ ### Editor mode (for visual-editor users only)
799
+
800
+ The visual editor's `World/HUD` toggle pauses the active world scene and lets the user drag widgets / edit anchor + offset / live-tune visual fields. Edits flow back to `public/scenes/hud/<id>.json` on save (Cmd+S / Edit→Play / Publish). Auto-save during editing only persists JSON to disk — no rebuild — so the iframe doesn't reload mid-edit.
801
+
802
+ For game code, the editor is invisible — you write HUD scene JSON the same way regardless of whether the user uses the editor.
803
+
629
804
  ## Anti-patterns (don't do these)
630
805
 
631
806
  - Do **not** call `Unboxy.init()` inside a scene. Initialize at module load in `main.ts` and export the promise.
@@ -638,6 +813,17 @@ The `scene-data-architecture` agent skill has the full guidance. The `scene-data
638
813
 
639
814
  ## Changelog
640
815
 
816
+ - **0.2.33** — docs: HUD scenes chapter added to this guide (covers slice-5 widget kinds, anchor model, dynamic bindings via `scene.registry`, `hud:press` event). Existing workspaces pick this up via `npm update @unboxy/phaser-sdk`.
817
+ - **0.2.32** — fix: 9-grid anchor side change in the visual editor moved container-based widgets (icon-button, progress-bar, panel) far from the new corner. `applyHudPatch` now re-applies the origin shift that compensates for containers having no `setOrigin`.
818
+ - **0.2.31** — fix: progress-bar and panel widgets drew their Graphics rect from `(0, 0)` instead of centered, so the visual was offset by `(w/2, h/2)` from the editor's selection rect. Both now draw centered.
819
+ - **0.2.30** — added HUD `progress-bar` and `panel` widgets (visual editor slice 5.5). progress-bar supports independent static/dynamic `value` and `max` bindings; panel is a colored rectangle with optional border + rounded corners. New types: `HudProgressBarVisual`, `HudPanelVisual`, `HudNumberSource`. See "HUD scenes" chapter above.
820
+ - **0.2.29** — added HUD scene runtime (visual editor slice 5). New module `src/scene/HudRuntime.ts` exporting `UnboxyHudScene`, `loadHudScene`, `spawnHudEntity`, `resolveAnchor`. Three v1 widget kinds: `text` (static or dynamic), `image`, `icon-button`. Auto-launched by `loadWorldScene` when `manifest.scenes[].hud` is set. Dynamic bindings hook into Phaser's game registry. Editor protocol gains `unboxy:editor:setEditMode { mode: 'world' \| 'hud' }`. See "HUD scenes" chapter above.
821
+ - **0.2.28** — fix: `EditorBridge.computeScreenRect` was multiplying by `Phaser.Scale.ScaleManager.displayScale.x` which is `gameSize / canvasBoundsSize` (the inverse of what the variable name suggests), so coords ballooned out of the iframe. Now measures CSS-to-logical scale directly via `canvas.getBoundingClientRect()`.
822
+ - **0.2.27** — added `EditorSelectionRectMessage` (visual editor slice 4 — inline AI popover). SDK posts `unboxy:editor:selectionRect { entityId, rect }` on selection change / drag-end / pan-zoom; rect is in iframe-relative pixels. Used by home-ui to anchor a floating ✨ button next to the selected entity for entity-scoped AI prompts.
823
+ - **0.2.26** — fix: tilemap entities couldn't be selected in the editor. `spawnEntity` for `tilemap` now sets explicit hit-test bounds (Phaser Graphics has no intrinsic size).
824
+ - **0.2.25** — fix: Backspace / Delete in the editor didn't remove selected entities once focus was inside the iframe. The overlay scene now catches bare Backspace/Delete and posts `action: 'delete'` to the host.
825
+ - **0.2.24** — fix: re-dragging an already-in-manifest asset spawned an unrendered entity. `createEntity` resolver now falls back to the cached `manifest.json` when the host omits `manifestAsset`.
826
+ - **0.2.23** — fix: code-rendered entities couldn't be selected/dragged in the editor. `spawnEntity` now sets explicit Graphics size (Phaser Graphics returns 0×0 from `getBounds()` otherwise). Schema gains optional `visual.width` / `visual.height`.
641
827
  - **0.2.22** — render-script registry wired (visual editor slice 3.5). `createUnboxyGame` accepts a new `renderScripts: Record<path, RenderScriptModule>` option; templates build it via `import.meta.glob('./visuals/*.ts', { eager: true })`. `loadWorldScene` falls back to that registry when no explicit `resolveRenderScript` is passed, so games don't have to plumb it through every scene call. Missing scripts no longer crash the scene boot — `spawnEntity` renders a clear orange-bordered "?" placeholder instead and tags the GameObject with `renderScriptMissing` for debug. `applyEdit` now handles `visual.params` patches on `code-rendered` entities by re-calling render with merged params (live editor preview as you tune in the Inspector). New exports: `RenderScriptModule`, `setRenderScriptRegistry`, `getRenderScriptRegistry`, `resolveRenderScript`. Pairs with the `phaser-render-script` agent skill which teaches the pure-function contract.
642
828
  - **0.2.21** — visual editor slice 3 (drag-to-place). Formalized trigger + tilemap entity data shapes (TriggerEntity now has `shape: rect|circle`, `description`, `targets[]`; TilemapEntity has `tileSize`, `size`, `layers[]`). spawnEntity renders both as placeholders (trigger = semi-transparent cyan rect/circle, tilemap = translucent grid sketch — full features ship in slices 5+6). New protocol: `unboxy:editor:createEntity { entity, manifestAsset? }` (host-side drag-to-place spawns entity at world coord; lazy-loads asset texture if needed) + `unboxy:editor:deleteEntity { entityId }` (Backspace + undo of create). `sceneLoaded` now also carries the manifest snapshot so home-ui can mutate the asset table when a new asset gets dropped on the canvas.
643
829
  - **0.2.20** — added editor shortcut forwarding (Cmd+Z/Y/S land in iframe when canvas focused; SDK posts back to host via `unboxy:editor:shortcut`). Without this, native shortcuts didn't reach the host's keydown listener after the user clicked the canvas.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboxy/phaser-sdk",
3
- "version": "0.2.32",
3
+ "version": "0.2.33",
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",