@unboxy/phaser-sdk 0.2.22 → 0.2.24
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,6 +2,7 @@ import Phaser from 'phaser';
|
|
|
2
2
|
import { getEntityRegistry } from '../scene/EntityRegistry.js';
|
|
3
3
|
import { parseColor, spawnEntity } from '../scene/spawnEntity.js';
|
|
4
4
|
import { resolveRenderScript } from '../scene/renderScripts.js';
|
|
5
|
+
import { getManifest } from '../scene/SceneLoader.js';
|
|
5
6
|
import { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './EditorOverlayScene.js';
|
|
6
7
|
import { getEditorState, setEditorActive, setSelection, } from './EditorState.js';
|
|
7
8
|
/**
|
|
@@ -209,15 +210,15 @@ function hitTest(game, worldX, worldY) {
|
|
|
209
210
|
let topmost = null;
|
|
210
211
|
let topmostDepth = -Infinity;
|
|
211
212
|
for (const go of registry.all()) {
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
213
|
+
const r = entityHitRect(go);
|
|
214
|
+
if (!r)
|
|
214
215
|
continue;
|
|
215
|
-
const r = withBounds.getBounds();
|
|
216
216
|
if (worldX < r.x || worldX > r.x + r.width)
|
|
217
217
|
continue;
|
|
218
218
|
if (worldY < r.y || worldY > r.y + r.height)
|
|
219
219
|
continue;
|
|
220
|
-
const
|
|
220
|
+
const withDepth = go;
|
|
221
|
+
const depth = typeof withDepth.depth === 'number' ? withDepth.depth : 0;
|
|
221
222
|
if (depth >= topmostDepth) {
|
|
222
223
|
topmostDepth = depth;
|
|
223
224
|
topmost = go;
|
|
@@ -225,6 +226,37 @@ function hitTest(game, worldX, worldY) {
|
|
|
225
226
|
}
|
|
226
227
|
return topmost;
|
|
227
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Compute a hit-test rectangle for a spawned entity in world coords.
|
|
231
|
+
*
|
|
232
|
+
* 1. If the entity has `editorHitWidth` / `editorHitHeight` set in data
|
|
233
|
+
* (code-rendered entities — Graphics has no intrinsic bounds), use
|
|
234
|
+
* those centered on the entity's x/y.
|
|
235
|
+
* 2. Otherwise fall back to Phaser's `getBounds()` (works for Sprite,
|
|
236
|
+
* Rectangle, Arc, Container — anything with a Size component).
|
|
237
|
+
*
|
|
238
|
+
* Returns null if neither path yields a usable rect.
|
|
239
|
+
*/
|
|
240
|
+
function entityHitRect(go) {
|
|
241
|
+
const hitW = go.getData('editorHitWidth');
|
|
242
|
+
const hitH = go.getData('editorHitHeight');
|
|
243
|
+
if (typeof hitW === 'number' && typeof hitH === 'number') {
|
|
244
|
+
const positioned = go;
|
|
245
|
+
return {
|
|
246
|
+
x: positioned.x - hitW / 2,
|
|
247
|
+
y: positioned.y - hitH / 2,
|
|
248
|
+
width: hitW,
|
|
249
|
+
height: hitH,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const withBounds = go;
|
|
253
|
+
if (typeof withBounds.getBounds !== 'function')
|
|
254
|
+
return null;
|
|
255
|
+
const r = withBounds.getBounds();
|
|
256
|
+
if (r.width === 0 && r.height === 0)
|
|
257
|
+
return null;
|
|
258
|
+
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
259
|
+
}
|
|
228
260
|
function findRegistry(game) {
|
|
229
261
|
for (const scene of game.scene.getScenes(false)) {
|
|
230
262
|
const reg = getEntityRegistry(scene);
|
|
@@ -353,23 +385,49 @@ async function createEntity(game, entity, manifestAsset) {
|
|
|
353
385
|
console.warn('[unboxy/editor] createEntity: world scene has no entity registry');
|
|
354
386
|
return;
|
|
355
387
|
}
|
|
388
|
+
// Resolve which AssetRecord to use for the lazy-load probe. Three paths:
|
|
389
|
+
// 1. Host passed `manifestAsset` (asset is brand-new) — use that.
|
|
390
|
+
// 2. Already in manifest but not provided (re-drag of existing asset)
|
|
391
|
+
// — look it up via the cached manifest.
|
|
392
|
+
// 3. Non-sprite entity (no asset needed) — leave undefined.
|
|
393
|
+
let assetForLoad = manifestAsset;
|
|
394
|
+
if (!assetForLoad && entity.kind === 'sprite') {
|
|
395
|
+
const visual = entity.visual;
|
|
396
|
+
const assetId = visual?.assetId;
|
|
397
|
+
if (assetId) {
|
|
398
|
+
try {
|
|
399
|
+
const manifest = getManifest(scene);
|
|
400
|
+
assetForLoad = manifest.assets.find((a) => a.id === assetId);
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
/* manifest not in cache — should not happen in edit mode */
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
356
407
|
// Lazy-load texture if needed (sprite + asset not yet in cache).
|
|
357
|
-
if (
|
|
408
|
+
if (assetForLoad &&
|
|
358
409
|
entity.kind === 'sprite' &&
|
|
359
|
-
!scene.textures.exists(
|
|
360
|
-
await loadAssetIntoScene(scene,
|
|
410
|
+
!scene.textures.exists(assetForLoad.textureKey)) {
|
|
411
|
+
await loadAssetIntoScene(scene, assetForLoad);
|
|
361
412
|
}
|
|
362
413
|
const ctx = {
|
|
363
414
|
scene,
|
|
364
415
|
registry,
|
|
365
416
|
resolveAsset: (id) => {
|
|
366
|
-
// For ad-hoc creation we may not have full manifest access. Rely on
|
|
367
|
-
// the host to have stamped the right textureKey/path into the asset
|
|
368
|
-
// we received via manifestAsset; fallback to a synthetic record so
|
|
369
|
-
// spawnEntity can still find a textureKey.
|
|
370
417
|
if (manifestAsset && manifestAsset.id === id)
|
|
371
418
|
return manifestAsset;
|
|
372
|
-
|
|
419
|
+
// Fallback: look up in the manifest cache. Covers re-drag of an
|
|
420
|
+
// already-in-manifest asset where the host omitted manifestAsset.
|
|
421
|
+
try {
|
|
422
|
+
const manifest = getManifest(scene);
|
|
423
|
+
const found = manifest.assets.find((a) => a.id === id);
|
|
424
|
+
if (found)
|
|
425
|
+
return found;
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
/* fall through to throw */
|
|
429
|
+
}
|
|
430
|
+
throw new Error(`[unboxy/editor] createEntity: asset '${id}' not found in manifest`);
|
|
373
431
|
},
|
|
374
432
|
resolveRenderScript: undefined,
|
|
375
433
|
};
|
|
@@ -190,10 +190,24 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
function computeBounds(go) {
|
|
193
|
-
//
|
|
193
|
+
// Code-rendered entities stash hit dimensions on data because Phaser
|
|
194
|
+
// Graphics has no intrinsic bounds. Use those when present.
|
|
195
|
+
const hitW = go.getData('editorHitWidth');
|
|
196
|
+
const hitH = go.getData('editorHitHeight');
|
|
197
|
+
if (typeof hitW === 'number' && typeof hitH === 'number') {
|
|
198
|
+
const positioned = go;
|
|
199
|
+
return {
|
|
200
|
+
x: positioned.x - hitW / 2,
|
|
201
|
+
y: positioned.y - hitH / 2,
|
|
202
|
+
width: hitW,
|
|
203
|
+
height: hitH,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
194
206
|
const withBounds = go;
|
|
195
207
|
if (typeof withBounds.getBounds !== 'function')
|
|
196
208
|
return null;
|
|
197
209
|
const r = withBounds.getBounds();
|
|
210
|
+
if (r.width === 0 && r.height === 0)
|
|
211
|
+
return null;
|
|
198
212
|
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
199
213
|
}
|
|
@@ -94,6 +94,8 @@ function createGroup(ctx, entity) {
|
|
|
94
94
|
return container;
|
|
95
95
|
}
|
|
96
96
|
function createCodeRendered(ctx, entity) {
|
|
97
|
+
const w = entity.visual.width ?? 64;
|
|
98
|
+
const h = entity.visual.height ?? 64;
|
|
97
99
|
const render = ctx.resolveRenderScript?.(entity.visual.script);
|
|
98
100
|
if (!render) {
|
|
99
101
|
// Slice 3.5: render scripts have a registry now, but a missing entry
|
|
@@ -102,21 +104,35 @@ function createCodeRendered(ctx, entity) {
|
|
|
102
104
|
// the user sees the entity exists but the visual is unresolved.
|
|
103
105
|
const ph = ctx.scene.add.graphics();
|
|
104
106
|
ph.fillStyle(0x666666, 0.4);
|
|
105
|
-
ph.fillRect(-
|
|
107
|
+
ph.fillRect(-w / 2, -h / 2, w, h);
|
|
106
108
|
ph.lineStyle(2, 0xff8800, 0.9);
|
|
107
|
-
ph.strokeRect(-
|
|
108
|
-
|
|
109
|
+
ph.strokeRect(-w / 2, -h / 2, w, h);
|
|
110
|
+
sizeForHitTest(ph, w, h);
|
|
109
111
|
ph.setData('renderScriptMissing', entity.visual.script);
|
|
110
112
|
return ph;
|
|
111
113
|
}
|
|
112
114
|
const g = ctx.scene.add.graphics();
|
|
113
115
|
const params = entity.visual.params ?? {};
|
|
114
116
|
render(g, params);
|
|
117
|
+
sizeForHitTest(g, w, h);
|
|
115
118
|
// Stash data needed for live re-render from EditorBridge.applyEdit.
|
|
116
119
|
g.setData('renderScriptPath', entity.visual.script);
|
|
117
120
|
g.setData('renderScriptParams', params);
|
|
118
121
|
return g;
|
|
119
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Phaser Graphics doesn't track its drawn area — `getBounds()` returns
|
|
125
|
+
* a 0×0 rect because the Graphics class doesn't include the Size/Origin
|
|
126
|
+
* mixins. Rather than fight Phaser's class hierarchy with method casts,
|
|
127
|
+
* stash the editor hit area on the GameObject's data manager. The editor
|
|
128
|
+
* overlay reads these before falling back to `getBounds()`. The implicit
|
|
129
|
+
* convention: hit area is centered on the GameObject's x/y, matching how
|
|
130
|
+
* render scripts conventionally draw around (0, 0).
|
|
131
|
+
*/
|
|
132
|
+
function sizeForHitTest(g, width, height) {
|
|
133
|
+
g.setData('editorHitWidth', width);
|
|
134
|
+
g.setData('editorHitHeight', height);
|
|
135
|
+
}
|
|
120
136
|
function applyTransform(go, t) {
|
|
121
137
|
// Most game objects implement Transform; Container does too. Cast through
|
|
122
138
|
// a permissive shape so this works for sprite/rect/circle/container alike.
|
package/dist/scene/types.d.ts
CHANGED
|
@@ -116,6 +116,14 @@ export interface CodeRenderedVisual {
|
|
|
116
116
|
/** Path to render script, e.g. `"src/visuals/boss-renderer.ts"`. */
|
|
117
117
|
script: string;
|
|
118
118
|
params?: Record<string, unknown>;
|
|
119
|
+
/**
|
|
120
|
+
* Optional bounds used for editor hit-testing (click + drag) and the
|
|
121
|
+
* selection rectangle. Phaser Graphics has no intrinsic size, so we set
|
|
122
|
+
* it explicitly. Defaults to 64×64 centered on the entity. The agent can
|
|
123
|
+
* override per-entity when the script draws something larger or smaller.
|
|
124
|
+
*/
|
|
125
|
+
width?: number;
|
|
126
|
+
height?: number;
|
|
119
127
|
}
|
|
120
128
|
export type WorldVisual = SpriteVisual | PrimitiveVisual | CodeRenderedVisual;
|
|
121
129
|
export type WorldEntityKind = 'sprite' | 'primitive' | 'code-rendered' | 'group' | 'tilemap' | 'trigger';
|