@unboxy/phaser-sdk 0.2.28 → 0.2.29
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/core/UnboxyGame.js +6 -1
- package/dist/editor/EditorBridge.js +113 -44
- package/dist/editor/EditorOverlayScene.d.ts +7 -2
- package/dist/editor/EditorOverlayScene.js +67 -16
- package/dist/editor/EditorState.d.ts +9 -0
- package/dist/editor/EditorState.js +7 -1
- package/dist/index.d.ts +2 -2
- package/dist/protocol.d.ts +55 -3
- package/dist/scene/EntityRegistry.d.ts +3 -0
- package/dist/scene/EntityRegistry.js +16 -0
- package/dist/scene/HudRuntime.d.ts +125 -0
- package/dist/scene/HudRuntime.js +717 -0
- package/dist/scene/SceneLoader.js +9 -0
- package/dist/scene/types.d.ts +101 -4
- package/package.json +1 -1
package/dist/core/UnboxyGame.js
CHANGED
|
@@ -4,6 +4,7 @@ import { setupRecordingListener } from '../recording/RecordingManager.js';
|
|
|
4
4
|
import { setupEditorModeListener } from '../scene/EditorMode.js';
|
|
5
5
|
import { ORIENTATION_DIMENSIONS } from '../orientation.js';
|
|
6
6
|
import { setRenderScriptRegistry, } from '../scene/renderScripts.js';
|
|
7
|
+
import { UnboxyHudScene } from '../scene/HudRuntime.js';
|
|
7
8
|
/**
|
|
8
9
|
* Create an Unboxy-enhanced Phaser game instance.
|
|
9
10
|
* Includes built-in integrations: screenshot capture, preserveDrawingBuffer, etc.
|
|
@@ -30,7 +31,11 @@ export function createUnboxyGame(options) {
|
|
|
30
31
|
arcade: { gravity: { x: 0, y: 0 }, debug: false },
|
|
31
32
|
},
|
|
32
33
|
...(options.plugins ? { plugins: options.plugins } : {}),
|
|
33
|
-
|
|
34
|
+
// UnboxyHudScene is auto-registered alongside the game's scenes so
|
|
35
|
+
// `loadWorldScene` can launch it when the active world scene's manifest
|
|
36
|
+
// entry sets `hud: '<id>'`. Registered at the end so the user's scenes
|
|
37
|
+
// own the boot order.
|
|
38
|
+
scene: [...options.scenes, UnboxyHudScene],
|
|
34
39
|
};
|
|
35
40
|
const game = new Phaser.Game(config);
|
|
36
41
|
// Built-in integrations
|
|
@@ -4,7 +4,8 @@ import { parseColor, spawnEntity } from '../scene/spawnEntity.js';
|
|
|
4
4
|
import { resolveRenderScript } from '../scene/renderScripts.js';
|
|
5
5
|
import { getManifest } from '../scene/SceneLoader.js';
|
|
6
6
|
import { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './EditorOverlayScene.js';
|
|
7
|
-
import { getEditorState, setEditorActive, setSelection, getSelection, } from './EditorState.js';
|
|
7
|
+
import { getEditorState, setEditorActive, setSelection, getSelection, getEditorMode, setEditorMode, } from './EditorState.js';
|
|
8
|
+
import { applyHudPatch, createHudEntityInScene, deleteHudEntityFromScene, findHudRegistry, findHudSceneFile, UNBOXY_HUD_SCENE_KEY, } from '../scene/HudRuntime.js';
|
|
8
9
|
/**
|
|
9
10
|
* EditorBridge wires the host (home-ui) to the iframe's Phaser game during
|
|
10
11
|
* Edit mode. Manages:
|
|
@@ -65,6 +66,16 @@ function handleMessage(game, msg) {
|
|
|
65
66
|
deleteEntity(game, msg.entityId);
|
|
66
67
|
postSelectionRect(game);
|
|
67
68
|
break;
|
|
69
|
+
case 'unboxy:editor:setEditMode':
|
|
70
|
+
// Slice 5 — World/HUD toggle. Mode change is purely state; the host
|
|
71
|
+
// already has both scene snapshots from enterEdit and just switches
|
|
72
|
+
// which one its UI renders. Selection may carry over between modes,
|
|
73
|
+
// but selectionRect re-posts so the ✨ button moves to the active
|
|
74
|
+
// mode's selected entity (or hides if none).
|
|
75
|
+
setEditorMode(game, msg.mode);
|
|
76
|
+
setSelection(game, null);
|
|
77
|
+
postSelectionRect(game);
|
|
78
|
+
break;
|
|
68
79
|
}
|
|
69
80
|
}
|
|
70
81
|
// --- Enter / exit ---------------------------------------------------------
|
|
@@ -156,16 +167,31 @@ function buildOverlayInit(game) {
|
|
|
156
167
|
// --- Snapshot -------------------------------------------------------------
|
|
157
168
|
const SNAPSHOT_POSTED_FLAG = '__unboxyEditorSnapshotPostedFor';
|
|
158
169
|
function postSceneSnapshot(game) {
|
|
170
|
+
const manifest = readActiveManifest(game);
|
|
159
171
|
const sceneFile = readActiveSceneFile(game);
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
172
|
+
if (sceneFile) {
|
|
173
|
+
postToHost({
|
|
174
|
+
type: 'unboxy:editor:sceneLoaded',
|
|
175
|
+
sceneId: sceneFile.id,
|
|
176
|
+
mode: 'world',
|
|
177
|
+
sceneFile,
|
|
178
|
+
manifest,
|
|
179
|
+
});
|
|
180
|
+
game[SNAPSHOT_POSTED_FLAG] = sceneFile.id;
|
|
181
|
+
}
|
|
182
|
+
// Also post the HUD scene if attached + loaded. The host stashes both
|
|
183
|
+
// snapshots so the World/HUD toggle is purely a UI switch — no fresh
|
|
184
|
+
// SDK round-trip needed when the user flips it.
|
|
185
|
+
const hudFile = findHudSceneFile(game);
|
|
186
|
+
if (hudFile) {
|
|
187
|
+
postToHost({
|
|
188
|
+
type: 'unboxy:editor:sceneLoaded',
|
|
189
|
+
sceneId: hudFile.id,
|
|
190
|
+
mode: 'hud',
|
|
191
|
+
sceneFile: hudFile,
|
|
192
|
+
manifest,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
169
195
|
}
|
|
170
196
|
// --- Selection rect (slice 4) ---------------------------------------------
|
|
171
197
|
//
|
|
@@ -217,13 +243,13 @@ function doPostSelectionRect(game) {
|
|
|
217
243
|
* so we factor in the canvas's parent offset too.
|
|
218
244
|
*/
|
|
219
245
|
function computeScreenRect(game, go) {
|
|
220
|
-
// Pull
|
|
246
|
+
// Pull entity-local bounds — same logic the editor uses for hit-test.
|
|
221
247
|
const hitW = go.getData('editorHitWidth');
|
|
222
248
|
const hitH = go.getData('editorHitHeight');
|
|
223
249
|
const positioned = go;
|
|
224
|
-
let
|
|
250
|
+
let entRect;
|
|
225
251
|
if (typeof hitW === 'number' && typeof hitH === 'number') {
|
|
226
|
-
|
|
252
|
+
entRect = {
|
|
227
253
|
x: positioned.x - hitW / 2,
|
|
228
254
|
y: positioned.y - hitH / 2,
|
|
229
255
|
width: hitW,
|
|
@@ -235,30 +261,40 @@ function computeScreenRect(game, go) {
|
|
|
235
261
|
if (typeof withBounds.getBounds !== 'function')
|
|
236
262
|
return null;
|
|
237
263
|
const r = withBounds.getBounds();
|
|
238
|
-
|
|
264
|
+
entRect = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
239
265
|
}
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
266
|
+
// HUD mode — entity coords are already in canvas-pixel space (the HUD
|
|
267
|
+
// scene has identity camera). Skip the world→canvas camera transform.
|
|
268
|
+
let canvasX, canvasY, canvasW, canvasH;
|
|
269
|
+
if (getEditorMode(game) === 'hud') {
|
|
270
|
+
canvasX = entRect.x;
|
|
271
|
+
canvasY = entRect.y;
|
|
272
|
+
canvasW = entRect.width;
|
|
273
|
+
canvasH = entRect.height;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// World mode — convert via the world scene's camera (scrollX/Y + zoom).
|
|
277
|
+
let cam = null;
|
|
278
|
+
for (const scene of game.scene.getScenes(false)) {
|
|
279
|
+
const key = scene.scene.key;
|
|
280
|
+
if (BOOT_SCENE_KEYS.has(key))
|
|
281
|
+
continue;
|
|
282
|
+
if (key === EDITOR_OVERLAY_KEY)
|
|
283
|
+
continue;
|
|
284
|
+
if (key === UNBOXY_HUD_SCENE_KEY)
|
|
285
|
+
continue;
|
|
286
|
+
if (getEntityRegistry(scene)) {
|
|
287
|
+
cam = scene.cameras.main;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
251
290
|
}
|
|
291
|
+
if (!cam)
|
|
292
|
+
return null;
|
|
293
|
+
canvasX = (entRect.x - cam.scrollX) * cam.zoom;
|
|
294
|
+
canvasY = (entRect.y - cam.scrollY) * cam.zoom;
|
|
295
|
+
canvasW = entRect.width * cam.zoom;
|
|
296
|
+
canvasH = entRect.height * cam.zoom;
|
|
252
297
|
}
|
|
253
|
-
if (!cam)
|
|
254
|
-
return null;
|
|
255
|
-
// World → canvas pixels (relative to canvas top-left, inside Phaser's
|
|
256
|
-
// logical canvas space — i.e. the game.config width/height, NOT the
|
|
257
|
-
// scaled visible pixels).
|
|
258
|
-
const canvasX = (worldRect.x - cam.scrollX) * cam.zoom;
|
|
259
|
-
const canvasY = (worldRect.y - cam.scrollY) * cam.zoom;
|
|
260
|
-
const canvasW = worldRect.width * cam.zoom;
|
|
261
|
-
const canvasH = worldRect.height * cam.zoom;
|
|
262
298
|
// Convert logical canvas pixels → CSS pixels using the canvas's actual
|
|
263
299
|
// displayed size (avoids depending on which direction Phaser's displayScale
|
|
264
300
|
// goes — we just measure it). Canvas may be letterboxed/pillarboxed inside
|
|
@@ -374,8 +410,18 @@ function entityHitRect(go) {
|
|
|
374
410
|
return null;
|
|
375
411
|
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
376
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Find the entity registry for the current editor mode. World mode picks the
|
|
415
|
+
* first non-Boot non-Editor scene with a registry; HUD mode picks the
|
|
416
|
+
* HUD scene's registry. Slice 5+.
|
|
417
|
+
*/
|
|
377
418
|
function findRegistry(game) {
|
|
419
|
+
if (getEditorMode(game) === 'hud')
|
|
420
|
+
return findHudRegistry(game);
|
|
378
421
|
for (const scene of game.scene.getScenes(false)) {
|
|
422
|
+
const key = scene.scene.key;
|
|
423
|
+
if (key === UNBOXY_HUD_SCENE_KEY)
|
|
424
|
+
continue;
|
|
379
425
|
const reg = getEntityRegistry(scene);
|
|
380
426
|
if (reg)
|
|
381
427
|
return reg;
|
|
@@ -384,6 +430,15 @@ function findRegistry(game) {
|
|
|
384
430
|
}
|
|
385
431
|
// --- applyEdit ------------------------------------------------------------
|
|
386
432
|
function applyEdit(game, entityId, patch) {
|
|
433
|
+
// HUD mode — patches modify anchor / HUD-visual fields. World-style
|
|
434
|
+
// transform / sprite-tint patches don't apply to HUD widgets, so we
|
|
435
|
+
// dispatch through a HUD-specific helper that knows the widget kinds.
|
|
436
|
+
if (getEditorMode(game) === 'hud') {
|
|
437
|
+
applyHudPatch(game, entityId, patch);
|
|
438
|
+
if (getSelection(game) === entityId)
|
|
439
|
+
postSelectionRect(game);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
387
442
|
const registry = findRegistry(game);
|
|
388
443
|
if (!registry)
|
|
389
444
|
return;
|
|
@@ -496,6 +551,17 @@ function applyVisualPatch(go, v) {
|
|
|
496
551
|
* sprite, which defeats the point of drag-to-place.
|
|
497
552
|
*/
|
|
498
553
|
async function createEntity(game, entity, manifestAsset) {
|
|
554
|
+
// HUD mode dispatches to the HUD-runtime spawner (slice 5). The host
|
|
555
|
+
// sends HudEntity records (not WorldEntity), so we type-erase.
|
|
556
|
+
if (getEditorMode(game) === 'hud') {
|
|
557
|
+
// Lazy-load asset if the HUD widget needs one and it's not in cache yet.
|
|
558
|
+
const hud = game.scene.getScene(UNBOXY_HUD_SCENE_KEY);
|
|
559
|
+
if (manifestAsset && hud && !hud.textures.exists(manifestAsset.textureKey)) {
|
|
560
|
+
await loadAssetIntoScene(hud, manifestAsset);
|
|
561
|
+
}
|
|
562
|
+
createHudEntityInScene(game, entity);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
499
565
|
const scene = findWorldScene(game);
|
|
500
566
|
if (!scene) {
|
|
501
567
|
console.warn('[unboxy/editor] createEntity: no world scene to spawn into');
|
|
@@ -622,6 +688,14 @@ function loadAssetIntoScene(scene, asset) {
|
|
|
622
688
|
});
|
|
623
689
|
}
|
|
624
690
|
function deleteEntity(game, entityId) {
|
|
691
|
+
// HUD mode — dispatch to the HUD-scene helper, which destroys + unregisters
|
|
692
|
+
// + drops the entity from the cached scene file.
|
|
693
|
+
if (getEditorMode(game) === 'hud') {
|
|
694
|
+
deleteHudEntityFromScene(game, entityId);
|
|
695
|
+
if (getSelection(game) === entityId)
|
|
696
|
+
setSelection(game, null);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
625
699
|
const scene = findWorldScene(game);
|
|
626
700
|
if (!scene)
|
|
627
701
|
return;
|
|
@@ -631,23 +705,18 @@ function deleteEntity(game, entityId) {
|
|
|
631
705
|
const go = registry.byId(entityId);
|
|
632
706
|
if (!go)
|
|
633
707
|
return;
|
|
634
|
-
// Remove from registry by clearing + rebuilding the entries we care about.
|
|
635
|
-
// The slice-1 EntityRegistry doesn't expose a direct removeById; the
|
|
636
|
-
// simplest thing is to destroy the GameObject and let stale registry
|
|
637
|
-
// entries dangle until the next scene reload (post-flush). Slice 3.5
|
|
638
|
-
// can add an explicit `registry.unregister` if it becomes a problem.
|
|
639
708
|
go.destroy();
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (state && state.selectedId === entityId)
|
|
644
|
-
state.selectedId = null;
|
|
709
|
+
registry.unregister(entityId);
|
|
710
|
+
if (getSelection(game) === entityId)
|
|
711
|
+
setSelection(game, null);
|
|
645
712
|
}
|
|
646
713
|
function findWorldScene(game) {
|
|
647
714
|
for (const scene of game.scene.getScenes(false)) {
|
|
648
715
|
const key = scene.scene.key;
|
|
649
716
|
if (BOOT_SCENE_KEYS.has(key))
|
|
650
717
|
continue;
|
|
718
|
+
if (key === UNBOXY_HUD_SCENE_KEY)
|
|
719
|
+
continue;
|
|
651
720
|
if (key === EDITOR_OVERLAY_KEY)
|
|
652
721
|
continue;
|
|
653
722
|
if (getEntityRegistry(scene))
|
|
@@ -61,6 +61,11 @@ export declare class EditorOverlayScene extends Phaser.Scene {
|
|
|
61
61
|
init(data: EditorOverlayInitData): void;
|
|
62
62
|
create(): void;
|
|
63
63
|
private handleShortcut;
|
|
64
|
+
/**
|
|
65
|
+
* Pointer coords mode-aware. World mode uses world coords (camera-relative);
|
|
66
|
+
* HUD mode uses canvas-pixel coords (HUD scene has identity camera).
|
|
67
|
+
*/
|
|
68
|
+
private pointerCoords;
|
|
64
69
|
private handlePointerDown;
|
|
65
70
|
private handlePointerMove;
|
|
66
71
|
private handlePointerUp;
|
|
@@ -72,8 +77,8 @@ export declare class EditorOverlayScene extends Phaser.Scene {
|
|
|
72
77
|
*/
|
|
73
78
|
private findWorldSceneCamera;
|
|
74
79
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
80
|
+
* Mode-aware registry lookup. World mode skips the HUD scene; HUD mode
|
|
81
|
+
* picks the HUD scene specifically.
|
|
77
82
|
*/
|
|
78
83
|
private findEntityRegistry;
|
|
79
84
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Phaser from 'phaser';
|
|
2
2
|
import { getEntityRegistry } from '../scene/EntityRegistry.js';
|
|
3
|
-
import {
|
|
3
|
+
import { findHudEntity, findHudRegistry, UNBOXY_HUD_SCENE_KEY, } from '../scene/HudRuntime.js';
|
|
4
|
+
import { getEditorState, startDrag, clearDrag, getDrag, getSelection, getEditorMode, } from './EditorState.js';
|
|
4
5
|
/**
|
|
5
6
|
* Editor overlay — slice 2.
|
|
6
7
|
*
|
|
@@ -94,13 +95,21 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Pointer coords mode-aware. World mode uses world coords (camera-relative);
|
|
100
|
+
* HUD mode uses canvas-pixel coords (HUD scene has identity camera).
|
|
101
|
+
*/
|
|
102
|
+
pointerCoords(pointer) {
|
|
103
|
+
if (getEditorMode(this.game) === 'hud')
|
|
104
|
+
return { x: pointer.x, y: pointer.y };
|
|
105
|
+
return { x: pointer.worldX, y: pointer.worldY };
|
|
106
|
+
}
|
|
97
107
|
handlePointerDown(pointer) {
|
|
98
108
|
const state = getEditorState(this.game);
|
|
99
109
|
if (!state.active)
|
|
100
110
|
return;
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const hit = this.hitTest(wx, wy);
|
|
111
|
+
const { x: px, y: py } = this.pointerCoords(pointer);
|
|
112
|
+
const hit = this.hitTest(px, py);
|
|
104
113
|
const event = pointer.event;
|
|
105
114
|
const modifiers = {
|
|
106
115
|
shift: !!event?.shiftKey,
|
|
@@ -118,8 +127,21 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
118
127
|
}
|
|
119
128
|
this.postPick(entityId, modifiers);
|
|
120
129
|
// Begin drag immediately on pointerdown (drag threshold can be added later).
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
// In HUD mode `startEntity` carries the entity's anchor offset (not the
|
|
131
|
+
// GameObject's canvas-pixel position) so dragEnd can report the new
|
|
132
|
+
// offset values directly.
|
|
133
|
+
if (getEditorMode(this.game) === 'hud') {
|
|
134
|
+
const ent = findHudEntity(this.game, entityId);
|
|
135
|
+
const startOffset = {
|
|
136
|
+
x: ent?.anchor.offsetX ?? 0,
|
|
137
|
+
y: ent?.anchor.offsetY ?? 0,
|
|
138
|
+
};
|
|
139
|
+
startDrag(this.game, entityId, { x: px, y: py }, startOffset);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const target = hit;
|
|
143
|
+
startDrag(this.game, entityId, { x: px, y: py }, { x: target.x, y: target.y });
|
|
144
|
+
}
|
|
123
145
|
}
|
|
124
146
|
handlePointerMove(pointer) {
|
|
125
147
|
const drag = getDrag(this.game);
|
|
@@ -131,22 +153,46 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
131
153
|
const go = registry.byId(drag.entityId);
|
|
132
154
|
if (!go)
|
|
133
155
|
return;
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
156
|
+
const { x: px, y: py } = this.pointerCoords(pointer);
|
|
157
|
+
const dx = px - drag.startWorld.x;
|
|
158
|
+
const dy = py - drag.startWorld.y;
|
|
159
|
+
if (getEditorMode(this.game) === 'hud') {
|
|
160
|
+
// HUD `startEntity` carries the offset, not the canvas-pixel start
|
|
161
|
+
// pos. We track per-frame delta on the GO so each pointermove just
|
|
162
|
+
// applies the incremental nudge — `go.x = startGoPos.x + dx` doesn't
|
|
163
|
+
// work because startGoPos isn't stored. The visual position the user
|
|
164
|
+
// sees is the canvas pos derived from the live anchor base + delta.
|
|
165
|
+
const lastDx = go.__lastDragDx ?? 0;
|
|
166
|
+
const lastDy = go.__lastDragDy ?? 0;
|
|
167
|
+
go.x += dx - lastDx;
|
|
168
|
+
go.y += dy - lastDy;
|
|
169
|
+
go.__lastDragDx = dx;
|
|
170
|
+
go.__lastDragDy = dy;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
go.x = drag.startEntity.x + dx;
|
|
174
|
+
go.y = drag.startEntity.y + dy;
|
|
175
|
+
}
|
|
138
176
|
}
|
|
139
177
|
handlePointerUp(pointer) {
|
|
140
178
|
const drag = getDrag(this.game);
|
|
141
179
|
if (!drag)
|
|
142
180
|
return;
|
|
143
|
-
const
|
|
144
|
-
const
|
|
181
|
+
const { x: px, y: py } = this.pointerCoords(pointer);
|
|
182
|
+
const dx = px - drag.startWorld.x;
|
|
183
|
+
const dy = py - drag.startWorld.y;
|
|
145
184
|
const before = drag.startEntity;
|
|
146
185
|
const after = { x: drag.startEntity.x + dx, y: drag.startEntity.y + dy };
|
|
186
|
+
// Reset the per-drag delta accumulator on the GO if HUD mode left one.
|
|
187
|
+
if (getEditorMode(this.game) === 'hud') {
|
|
188
|
+
const reg = this.findEntityRegistry();
|
|
189
|
+
const go = reg?.byId(drag.entityId);
|
|
190
|
+
if (go) {
|
|
191
|
+
go.__lastDragDx = 0;
|
|
192
|
+
go.__lastDragDy = 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
147
195
|
clearDrag(this.game);
|
|
148
|
-
// Suppress dragEnd for trivial "click without movement" — the host already
|
|
149
|
-
// got pickEntity for those.
|
|
150
196
|
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5)
|
|
151
197
|
return;
|
|
152
198
|
this.postDragEnd(drag.entityId, before, after);
|
|
@@ -184,11 +230,16 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
184
230
|
return null;
|
|
185
231
|
}
|
|
186
232
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
233
|
+
* Mode-aware registry lookup. World mode skips the HUD scene; HUD mode
|
|
234
|
+
* picks the HUD scene specifically.
|
|
189
235
|
*/
|
|
190
236
|
findEntityRegistry() {
|
|
237
|
+
if (getEditorMode(this.game) === 'hud')
|
|
238
|
+
return findHudRegistry(this.game);
|
|
191
239
|
for (const scene of this.game.scene.getScenes(false)) {
|
|
240
|
+
const key = scene.scene.key;
|
|
241
|
+
if (key === UNBOXY_HUD_SCENE_KEY)
|
|
242
|
+
continue;
|
|
192
243
|
const reg = getEntityRegistry(scene);
|
|
193
244
|
if (reg)
|
|
194
245
|
return reg;
|
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
* they reach game systems; this state carries the selection + drag-in-progress
|
|
7
7
|
* info both sides read.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Editor mode — slice 5. World mode edits the active world scene; HUD mode
|
|
11
|
+
* edits the HUD scene attached to that world (manifest's `scenes[].hud`).
|
|
12
|
+
* Toggled by host via `unboxy:editor:setEditMode`.
|
|
13
|
+
*/
|
|
14
|
+
export type EditorMode = 'world' | 'hud';
|
|
9
15
|
interface EditorStateShape {
|
|
10
16
|
active: boolean;
|
|
11
17
|
selectedId: string | null;
|
|
18
|
+
mode: EditorMode;
|
|
12
19
|
/**
|
|
13
20
|
* Drag-in-progress info. While set, the overlay scene mutates the entity's
|
|
14
21
|
* x/y directly per pointermove without going through the host. On
|
|
@@ -35,6 +42,8 @@ interface EditorStateShape {
|
|
|
35
42
|
type AnyObject = object;
|
|
36
43
|
export declare function getEditorState(game: AnyObject): EditorStateShape;
|
|
37
44
|
export declare function setEditorActive(game: AnyObject, active: boolean): void;
|
|
45
|
+
export declare function getEditorMode(game: AnyObject): EditorMode;
|
|
46
|
+
export declare function setEditorMode(game: AnyObject, mode: EditorMode): void;
|
|
38
47
|
export declare function setSelection(game: AnyObject, id: string | null): void;
|
|
39
48
|
export declare function getSelection(game: AnyObject): string | null;
|
|
40
49
|
export declare function startDrag(game: AnyObject, entityId: string, startWorld: {
|
|
@@ -15,13 +15,19 @@ export function getEditorState(game) {
|
|
|
15
15
|
const existing = b[KEY];
|
|
16
16
|
if (existing)
|
|
17
17
|
return existing;
|
|
18
|
-
const fresh = { active: false, selectedId: null, drag: null };
|
|
18
|
+
const fresh = { active: false, selectedId: null, mode: 'world', drag: null };
|
|
19
19
|
b[KEY] = fresh;
|
|
20
20
|
return fresh;
|
|
21
21
|
}
|
|
22
22
|
export function setEditorActive(game, active) {
|
|
23
23
|
getEditorState(game).active = active;
|
|
24
24
|
}
|
|
25
|
+
export function getEditorMode(game) {
|
|
26
|
+
return getEditorState(game).mode;
|
|
27
|
+
}
|
|
28
|
+
export function setEditorMode(game, mode) {
|
|
29
|
+
getEditorState(game).mode = mode;
|
|
30
|
+
}
|
|
25
31
|
export function setSelection(game, id) {
|
|
26
32
|
getEditorState(game).selectedId = id;
|
|
27
33
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { setRenderScriptRegistry, getRenderScriptRegistry, resolveRenderScript,
|
|
|
26
26
|
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
|
-
export type { EditorEntityPatch, EditorEnterMessage, EditorExitMessage, EditorGetSceneMessage, EditorApplyEditMessage, EditorSetSelectionMessage, EditorPanZoomMessage, EditorHostToSdkMessage, EditorSceneLoadedMessage, EditorSelectionPickedMessage, EditorDragEndMessage, EditorShortcutMessage, EditorCreateEntityMessage, EditorDeleteEntityMessage, EditorSelectionRectMessage, EditorSdkToHostMessage, } from './protocol.js';
|
|
29
|
+
export type { EditorEntityPatch, EditorEnterMessage, EditorExitMessage, EditorGetSceneMessage, EditorApplyEditMessage, EditorSetSelectionMessage, EditorPanZoomMessage, EditorHostToSdkMessage, EditorSceneLoadedMessage, EditorSelectionPickedMessage, EditorDragEndMessage, EditorShortcutMessage, EditorCreateEntityMessage, EditorDeleteEntityMessage, EditorSetEditModeMessage, EditorSelectionRectMessage, EditorSdkToHostMessage, } from './protocol.js';
|
|
30
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, Transform, Anchor, AnchorSide, } 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, HudVisual, HudTextVisual, HudImageVisual, HudIconButtonVisual, HudTextSource, HudLayer, Transform, Anchor, AnchorSide, } 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/protocol.d.ts
CHANGED
|
@@ -61,13 +61,26 @@ export interface EditorEntityPatch {
|
|
|
61
61
|
scaleY: number;
|
|
62
62
|
depth: number;
|
|
63
63
|
}>;
|
|
64
|
+
/**
|
|
65
|
+
* HUD-only (slice 5). Updates an entity's anchor side / offset. Only one
|
|
66
|
+
* of `transform` (world entities) or `anchor` (HUD entities) is set per
|
|
67
|
+
* patch; the SDK picks the right path based on the active mode.
|
|
68
|
+
*/
|
|
69
|
+
anchor?: Partial<{
|
|
70
|
+
side: string;
|
|
71
|
+
offsetX: number;
|
|
72
|
+
offsetY: number;
|
|
73
|
+
}>;
|
|
74
|
+
/** HUD-only (slice 5). Render layer / z-order overrides. */
|
|
75
|
+
layer?: string;
|
|
76
|
+
z?: number;
|
|
64
77
|
visual?: {
|
|
65
78
|
tint?: string | null;
|
|
66
79
|
alpha?: number;
|
|
67
80
|
flipX?: boolean;
|
|
68
81
|
flipY?: boolean;
|
|
69
82
|
frame?: string | number;
|
|
70
|
-
/** For primitives. */
|
|
83
|
+
/** For primitives + HUD widgets. */
|
|
71
84
|
width?: number;
|
|
72
85
|
height?: number;
|
|
73
86
|
radius?: number;
|
|
@@ -80,6 +93,25 @@ export interface EditorEntityPatch {
|
|
|
80
93
|
* Slice 3.5.
|
|
81
94
|
*/
|
|
82
95
|
params?: Record<string, unknown>;
|
|
96
|
+
/**
|
|
97
|
+
* HUD text widgets (slice 5). Replaces `visual.source` wholesale —
|
|
98
|
+
* static→dynamic switches and prefix/suffix edits land here. Static
|
|
99
|
+
* mode also accepts `text`; dynamic accepts `binding`/`prefix`/`suffix`/`fallback`.
|
|
100
|
+
*/
|
|
101
|
+
source?: Record<string, unknown>;
|
|
102
|
+
/** HUD text font / colour. */
|
|
103
|
+
fontFamily?: string;
|
|
104
|
+
fontSize?: number;
|
|
105
|
+
color?: string;
|
|
106
|
+
align?: 'left' | 'center' | 'right';
|
|
107
|
+
/** HUD image / icon-button asset. */
|
|
108
|
+
assetId?: string;
|
|
109
|
+
iconAssetId?: string;
|
|
110
|
+
/** HUD icon-button. */
|
|
111
|
+
label?: string;
|
|
112
|
+
shape?: 'rounded-rect' | 'circle';
|
|
113
|
+
pressedFillColor?: string;
|
|
114
|
+
textColor?: string;
|
|
83
115
|
};
|
|
84
116
|
role?: string | null;
|
|
85
117
|
properties?: Record<string, unknown>;
|
|
@@ -132,11 +164,31 @@ export interface EditorDeleteEntityMessage {
|
|
|
132
164
|
type: 'unboxy:editor:deleteEntity';
|
|
133
165
|
entityId: string;
|
|
134
166
|
}
|
|
135
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Editor mode — slice 5. World mode edits the world scene; HUD mode edits
|
|
169
|
+
* the HUD scene attached to the active world. The host sends this when the
|
|
170
|
+
* user toggles the World/HUD switch in the editor canvas.
|
|
171
|
+
*
|
|
172
|
+
* The mode change does NOT clear selection at the SDK level; the host owns
|
|
173
|
+
* selection state per mode (separate selectedId for world / HUD drafts).
|
|
174
|
+
*/
|
|
175
|
+
export interface EditorSetEditModeMessage {
|
|
176
|
+
type: 'unboxy:editor:setEditMode';
|
|
177
|
+
mode: 'world' | 'hud';
|
|
178
|
+
}
|
|
179
|
+
export type EditorHostToSdkMessage = EditorEnterMessage | EditorExitMessage | EditorGetSceneMessage | EditorApplyEditMessage | EditorSetSelectionMessage | EditorPanZoomMessage | EditorCreateEntityMessage | EditorDeleteEntityMessage | EditorSetEditModeMessage;
|
|
136
180
|
export interface EditorSceneLoadedMessage {
|
|
137
181
|
type: 'unboxy:editor:sceneLoaded';
|
|
138
182
|
sceneId: string;
|
|
139
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Discriminator (slice 5+). 'world' (default for back-compat) is the
|
|
185
|
+
* world scene; 'hud' is the HUD overlay scene. SDK posts one message per
|
|
186
|
+
* scene type when entering edit mode, so the host can populate both
|
|
187
|
+
* Hierarchy / Inspector drafts and switch between them via the
|
|
188
|
+
* World/HUD toggle without a fresh SDK round-trip.
|
|
189
|
+
*/
|
|
190
|
+
mode?: 'world' | 'hud';
|
|
191
|
+
/** Snapshot of the scene file's current entities. */
|
|
140
192
|
sceneFile: unknown;
|
|
141
193
|
/**
|
|
142
194
|
* Snapshot of the manifest (asset table + scene list). Slice 3+ — home-ui
|
|
@@ -14,6 +14,9 @@ export declare class EntityRegistry {
|
|
|
14
14
|
byId(id: string): Phaser.GameObjects.GameObject | undefined;
|
|
15
15
|
byRole(role: string): Phaser.GameObjects.GameObject[];
|
|
16
16
|
all(): Phaser.GameObjects.GameObject[];
|
|
17
|
+
/** Remove an entity from the registry. Does NOT destroy the GameObject —
|
|
18
|
+
* callers (editor delete path) destroy first, then unregister. */
|
|
19
|
+
unregister(id: string): void;
|
|
17
20
|
clear(): void;
|
|
18
21
|
}
|
|
19
22
|
export declare function attachEntityRegistry(scene: Phaser.Scene): EntityRegistry;
|
|
@@ -28,6 +28,22 @@ export class EntityRegistry {
|
|
|
28
28
|
all() {
|
|
29
29
|
return Array.from(this.byIdMap.values());
|
|
30
30
|
}
|
|
31
|
+
/** Remove an entity from the registry. Does NOT destroy the GameObject —
|
|
32
|
+
* callers (editor delete path) destroy first, then unregister. */
|
|
33
|
+
unregister(id) {
|
|
34
|
+
const go = this.byIdMap.get(id);
|
|
35
|
+
if (!go)
|
|
36
|
+
return;
|
|
37
|
+
this.byIdMap.delete(id);
|
|
38
|
+
for (const [role, list] of this.byRoleMap) {
|
|
39
|
+
const idx = list.indexOf(go);
|
|
40
|
+
if (idx >= 0) {
|
|
41
|
+
list.splice(idx, 1);
|
|
42
|
+
if (list.length === 0)
|
|
43
|
+
this.byRoleMap.delete(role);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
clear() {
|
|
32
48
|
this.byIdMap.clear();
|
|
33
49
|
this.byRoleMap.clear();
|