@unboxy/phaser-sdk 0.2.20 → 0.2.21
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/SDK-GUIDE.md +2 -0
- package/dist/editor/EditorBridge.js +162 -1
- package/dist/index.d.ts +2 -2
- package/dist/protocol.d.ts +24 -1
- package/dist/scene/spawnEntity.js +59 -2
- package/dist/scene/types.d.ts +47 -7
- package/package.json +1 -1
package/SDK-GUIDE.md
CHANGED
|
@@ -638,6 +638,8 @@ The `scene-data-architecture` agent skill has the full guidance. The `scene-data
|
|
|
638
638
|
|
|
639
639
|
## Changelog
|
|
640
640
|
|
|
641
|
+
- **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.
|
|
642
|
+
- **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.
|
|
641
643
|
- **0.2.19** — fix: editor `enter` arriving while BootScene is still preloading (the post-flush iframe-rebuild path) had nothing to pause, so the world scene resumed running freely once it started up. Now the bridge re-attempts the pause + scene snapshot for ~3 seconds after enter, catching the scene as it transitions from BootScene → GameScene. Snapshot is also re-posted once a scene file lands in cache. Surfaced when slice-2's auto-flush rebuilt the iframe and the user found the sprite started moving again post-save (2026-05-07).
|
|
642
644
|
- **0.2.18** — added editor bridge (visual editor slice 2). Replaces the slice-1 `unboxy:setEditMode` message with a full editor protocol: `unboxy:editor:enter`/`:exit` (toggles edit mode + launches the overlay scene), `:applyEdit` (mutates a live entity from the host), `:setSelection` (host pushes selection), `:panZoom` (editor camera control), plus SDK→host `:sceneLoaded` (initial snapshot), `:pickEntity` (pointer-down selection), `:dragEnd` (entity dragged to new position). New module `src/editor/` with `EditorOverlayScene` (high-depth Phaser.Scene drawing selection rect + world bounds, capturing pointer events) and `EditorBridge` (postMessage handler + applyEdit logic). New exports: `setupEditorBridge`, `EditorOverlayScene`, `EDITOR_OVERLAY_KEY`, plus all editor protocol types. `setupEditorModeListener` is preserved as a thin wrapper that delegates to the bridge so existing `createUnboxyGame` wiring continues to work. Pairs with home-ui's new Hierarchy + Inspector panels and `useEditorDraft` hook.
|
|
643
645
|
- **0.2.17** — added scene-as-data foundation (visual editor slice 1). New exports: `loadWorldScene`, `preloadManifest`, `getManifest`, `preloadSceneAssets`, `spawnEntity`, `EntityRegistry`, `attachEntityRegistry`, `getEntityRegistry`, `setupEditorModeListener`, `isEditMode`, `parseColor`, `SCHEMA_VERSION`, plus all schema types (`Manifest`, `WorldScene`, `WorldEntity`, `Transform`, `AssetRecord`, etc.). New games that ship with `public/scenes/manifest.json` load entities + camera config from JSON; `GameScene.ts` becomes a generic loader that calls `await loadWorldScene(this, sceneId)`. `createUnboxyGame` now also wires `setupEditorModeListener` so the host (home-ui) can pause active scenes via `postMessage({ type: 'unboxy:setEditMode', enabled: boolean })`. Purely additive — existing scene-as-code games are unaffected. Migration to scene-as-data is opt-in via the `scene-data-migration` agent skill.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import Phaser from 'phaser';
|
|
1
2
|
import { getEntityRegistry } from '../scene/EntityRegistry.js';
|
|
2
|
-
import { parseColor } from '../scene/spawnEntity.js';
|
|
3
|
+
import { parseColor, spawnEntity } from '../scene/spawnEntity.js';
|
|
3
4
|
import { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './EditorOverlayScene.js';
|
|
4
5
|
import { getEditorState, setEditorActive, setSelection, } from './EditorState.js';
|
|
5
6
|
/**
|
|
@@ -53,6 +54,12 @@ function handleMessage(game, msg) {
|
|
|
53
54
|
case 'unboxy:editor:panZoom':
|
|
54
55
|
applyPanZoom(game, msg);
|
|
55
56
|
break;
|
|
57
|
+
case 'unboxy:editor:createEntity':
|
|
58
|
+
void createEntity(game, msg.entity, msg.manifestAsset);
|
|
59
|
+
break;
|
|
60
|
+
case 'unboxy:editor:deleteEntity':
|
|
61
|
+
deleteEntity(game, msg.entityId);
|
|
62
|
+
break;
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
// --- Enter / exit ---------------------------------------------------------
|
|
@@ -151,9 +158,19 @@ function postSceneSnapshot(game) {
|
|
|
151
158
|
type: 'unboxy:editor:sceneLoaded',
|
|
152
159
|
sceneId: sceneFile.id,
|
|
153
160
|
sceneFile,
|
|
161
|
+
manifest: readActiveManifest(game),
|
|
154
162
|
});
|
|
155
163
|
game[SNAPSHOT_POSTED_FLAG] = sceneFile.id;
|
|
156
164
|
}
|
|
165
|
+
function readActiveManifest(game) {
|
|
166
|
+
for (const scene of game.scene.getScenes(false)) {
|
|
167
|
+
const cache = scene.cache.json;
|
|
168
|
+
const m = cache.entries?.entries?.['unboxy:manifest'];
|
|
169
|
+
if (m)
|
|
170
|
+
return m;
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
157
174
|
function hasPostedSnapshot(game) {
|
|
158
175
|
return !!game[SNAPSHOT_POSTED_FLAG];
|
|
159
176
|
}
|
|
@@ -292,6 +309,150 @@ function applyVisualPatch(go, v) {
|
|
|
292
309
|
}
|
|
293
310
|
}
|
|
294
311
|
}
|
|
312
|
+
// --- Create / delete ------------------------------------------------------
|
|
313
|
+
/**
|
|
314
|
+
* Spawn a new entity into the active world scene. Slice 3.
|
|
315
|
+
*
|
|
316
|
+
* If the entity references an asset whose texture isn't yet in the Phaser
|
|
317
|
+
* cache, we lazy-load it before spawn — same pattern as SceneLoader's
|
|
318
|
+
* preloadSceneAssets, but on a single asset and at edit time. Without this
|
|
319
|
+
* the host would have to coordinate a build before showing a dropped
|
|
320
|
+
* sprite, which defeats the point of drag-to-place.
|
|
321
|
+
*/
|
|
322
|
+
async function createEntity(game, entity, manifestAsset) {
|
|
323
|
+
const scene = findWorldScene(game);
|
|
324
|
+
if (!scene) {
|
|
325
|
+
console.warn('[unboxy/editor] createEntity: no world scene to spawn into');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const registry = getEntityRegistry(scene);
|
|
329
|
+
if (!registry) {
|
|
330
|
+
console.warn('[unboxy/editor] createEntity: world scene has no entity registry');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// Lazy-load texture if needed (sprite + asset not yet in cache).
|
|
334
|
+
if (manifestAsset &&
|
|
335
|
+
entity.kind === 'sprite' &&
|
|
336
|
+
!scene.textures.exists(manifestAsset.textureKey)) {
|
|
337
|
+
await loadAssetIntoScene(scene, manifestAsset);
|
|
338
|
+
}
|
|
339
|
+
const ctx = {
|
|
340
|
+
scene,
|
|
341
|
+
registry,
|
|
342
|
+
resolveAsset: (id) => {
|
|
343
|
+
// For ad-hoc creation we may not have full manifest access. Rely on
|
|
344
|
+
// the host to have stamped the right textureKey/path into the asset
|
|
345
|
+
// we received via manifestAsset; fallback to a synthetic record so
|
|
346
|
+
// spawnEntity can still find a textureKey.
|
|
347
|
+
if (manifestAsset && manifestAsset.id === id)
|
|
348
|
+
return manifestAsset;
|
|
349
|
+
throw new Error(`[unboxy/editor] createEntity: asset '${id}' not in manifest payload — host must include manifestAsset`);
|
|
350
|
+
},
|
|
351
|
+
resolveRenderScript: undefined,
|
|
352
|
+
};
|
|
353
|
+
try {
|
|
354
|
+
spawnEntity(ctx, entity);
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
console.warn('[unboxy/editor] spawnEntity failed:', e);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function loadAssetIntoScene(scene, asset) {
|
|
361
|
+
return new Promise((resolve, reject) => {
|
|
362
|
+
if (scene.textures.exists(asset.textureKey)) {
|
|
363
|
+
resolve();
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const onComplete = () => {
|
|
367
|
+
cleanup();
|
|
368
|
+
resolve();
|
|
369
|
+
};
|
|
370
|
+
const onError = (file) => {
|
|
371
|
+
if (file.key !== asset.textureKey)
|
|
372
|
+
return;
|
|
373
|
+
cleanup();
|
|
374
|
+
reject(new Error(`failed to load asset ${asset.id}: ${file.url}`));
|
|
375
|
+
};
|
|
376
|
+
const cleanup = () => {
|
|
377
|
+
scene.load.off(Phaser.Loader.Events.FILE_COMPLETE, perFileComplete);
|
|
378
|
+
scene.load.off(Phaser.Loader.Events.FILE_LOAD_ERROR, onError);
|
|
379
|
+
scene.load.off(Phaser.Loader.Events.COMPLETE, onComplete);
|
|
380
|
+
};
|
|
381
|
+
const perFileComplete = (key) => {
|
|
382
|
+
if (key === asset.textureKey) {
|
|
383
|
+
cleanup();
|
|
384
|
+
resolve();
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
scene.load.on(Phaser.Loader.Events.FILE_COMPLETE, perFileComplete);
|
|
388
|
+
scene.load.on(Phaser.Loader.Events.FILE_LOAD_ERROR, onError);
|
|
389
|
+
scene.load.on(Phaser.Loader.Events.COMPLETE, onComplete);
|
|
390
|
+
switch (asset.kind) {
|
|
391
|
+
case 'image':
|
|
392
|
+
scene.load.image(asset.textureKey, asset.path);
|
|
393
|
+
break;
|
|
394
|
+
case 'spritesheet':
|
|
395
|
+
if (asset.spriteSheetConfig) {
|
|
396
|
+
scene.load.spritesheet(asset.textureKey, asset.path, asset.spriteSheetConfig);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
scene.load.image(asset.textureKey, asset.path);
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
case 'atlas':
|
|
403
|
+
if (asset.atlasPath && asset.atlasFormat === 'xml') {
|
|
404
|
+
scene.load.atlasXML(asset.textureKey, asset.path, asset.atlasPath);
|
|
405
|
+
}
|
|
406
|
+
else if (asset.atlasPath) {
|
|
407
|
+
scene.load.atlas(asset.textureKey, asset.path, asset.atlasPath);
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
case 'audio':
|
|
411
|
+
scene.load.audio(asset.textureKey, asset.path);
|
|
412
|
+
break;
|
|
413
|
+
default:
|
|
414
|
+
cleanup();
|
|
415
|
+
reject(new Error(`unsupported asset kind for editor lazy-load: ${asset.kind}`));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (!scene.load.isLoading())
|
|
419
|
+
scene.load.start();
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function deleteEntity(game, entityId) {
|
|
423
|
+
const scene = findWorldScene(game);
|
|
424
|
+
if (!scene)
|
|
425
|
+
return;
|
|
426
|
+
const registry = getEntityRegistry(scene);
|
|
427
|
+
if (!registry)
|
|
428
|
+
return;
|
|
429
|
+
const go = registry.byId(entityId);
|
|
430
|
+
if (!go)
|
|
431
|
+
return;
|
|
432
|
+
// Remove from registry by clearing + rebuilding the entries we care about.
|
|
433
|
+
// The slice-1 EntityRegistry doesn't expose a direct removeById; the
|
|
434
|
+
// simplest thing is to destroy the GameObject and let stale registry
|
|
435
|
+
// entries dangle until the next scene reload (post-flush). Slice 3.5
|
|
436
|
+
// can add an explicit `registry.unregister` if it becomes a problem.
|
|
437
|
+
go.destroy();
|
|
438
|
+
// Drop selection if it pointed at this entity.
|
|
439
|
+
const sel = game;
|
|
440
|
+
const state = sel['__unboxyEditorState'];
|
|
441
|
+
if (state && state.selectedId === entityId)
|
|
442
|
+
state.selectedId = null;
|
|
443
|
+
}
|
|
444
|
+
function findWorldScene(game) {
|
|
445
|
+
for (const scene of game.scene.getScenes(false)) {
|
|
446
|
+
const key = scene.scene.key;
|
|
447
|
+
if (BOOT_SCENE_KEYS.has(key))
|
|
448
|
+
continue;
|
|
449
|
+
if (key === EDITOR_OVERLAY_KEY)
|
|
450
|
+
continue;
|
|
451
|
+
if (getEntityRegistry(scene))
|
|
452
|
+
return scene;
|
|
453
|
+
}
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
295
456
|
// --- Pan / zoom -----------------------------------------------------------
|
|
296
457
|
function applyPanZoom(game, msg) {
|
|
297
458
|
// Apply to BOTH the world scene's camera (so the entity rendering moves)
|
package/dist/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export { EntityRegistry, attachEntityRegistry, getEntityRegistry, } from './scen
|
|
|
24
24
|
export { setupEditorModeListener, isEditMode } from './scene/EditorMode.js';
|
|
25
25
|
export { setupEditorBridge } from './editor/EditorBridge.js';
|
|
26
26
|
export { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './editor/EditorOverlayScene.js';
|
|
27
|
-
export type { EditorEntityPatch, EditorEnterMessage, EditorExitMessage, EditorGetSceneMessage, EditorApplyEditMessage, EditorSetSelectionMessage, EditorPanZoomMessage, EditorHostToSdkMessage, EditorSceneLoadedMessage, EditorSelectionPickedMessage, EditorDragEndMessage, EditorShortcutMessage, EditorSdkToHostMessage, } from './protocol.js';
|
|
27
|
+
export type { EditorEntityPatch, EditorEnterMessage, EditorExitMessage, EditorGetSceneMessage, EditorApplyEditMessage, EditorSetSelectionMessage, EditorPanZoomMessage, EditorHostToSdkMessage, EditorSceneLoadedMessage, EditorSelectionPickedMessage, EditorDragEndMessage, EditorShortcutMessage, EditorCreateEntityMessage, EditorDeleteEntityMessage, EditorSdkToHostMessage, } from './protocol.js';
|
|
28
28
|
export { SCHEMA_VERSION, } from './scene/types.js';
|
|
29
|
-
export type { Manifest, SceneRef, HudRef, AssetRecord, AssetKind, SceneType, SceneFile, WorldScene, HudScene, WorldSceneConfig, CameraConfig, WorldEntity, WorldEntityKind, NonGroupWorldEntity, SpriteEntity, PrimitiveEntity, CodeRenderedEntity, GroupEntity, TilemapEntity, TriggerEntity, WorldVisual, SpriteVisual, PrimitiveVisual, PrimitiveRectVisual, PrimitiveCircleVisual, CodeRenderedVisual, Transform, Anchor, AnchorSide, } from './scene/types.js';
|
|
29
|
+
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';
|
|
30
30
|
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
|
@@ -108,12 +108,35 @@ export interface EditorPanZoomMessage {
|
|
|
108
108
|
/** If true, deltas are added to current scroll. */
|
|
109
109
|
relative?: boolean;
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Create a new entity in the active scene (slice 3 — drag-to-place).
|
|
113
|
+
* The SDK picks an asset record from `manifestAsset` (if present) so the
|
|
114
|
+
* texture can be lazy-loaded before spawn. Position can be supplied as
|
|
115
|
+
* world coords (preferred — host computed via 1:1 mapping in slice 3) or
|
|
116
|
+
* skipped if the entity already carries a transform.
|
|
117
|
+
*/
|
|
118
|
+
export interface EditorCreateEntityMessage {
|
|
119
|
+
type: 'unboxy:editor:createEntity';
|
|
120
|
+
/** Full entity record. transform.x/y is authoritative. */
|
|
121
|
+
entity: unknown;
|
|
122
|
+
/** Asset record to add to runtime cache before spawn. Omit if assetId is already loaded. */
|
|
123
|
+
manifestAsset?: unknown;
|
|
124
|
+
}
|
|
125
|
+
export interface EditorDeleteEntityMessage {
|
|
126
|
+
type: 'unboxy:editor:deleteEntity';
|
|
127
|
+
entityId: string;
|
|
128
|
+
}
|
|
129
|
+
export type EditorHostToSdkMessage = EditorEnterMessage | EditorExitMessage | EditorGetSceneMessage | EditorApplyEditMessage | EditorSetSelectionMessage | EditorPanZoomMessage | EditorCreateEntityMessage | EditorDeleteEntityMessage;
|
|
112
130
|
export interface EditorSceneLoadedMessage {
|
|
113
131
|
type: 'unboxy:editor:sceneLoaded';
|
|
114
132
|
sceneId: string;
|
|
115
133
|
/** Snapshot of the world scene file's current entities + camera. */
|
|
116
134
|
sceneFile: unknown;
|
|
135
|
+
/**
|
|
136
|
+
* Snapshot of the manifest (asset table + scene list). Slice 3+ — home-ui
|
|
137
|
+
* needs this to mutate the manifest when drag-to-place adds a new asset.
|
|
138
|
+
*/
|
|
139
|
+
manifest?: unknown;
|
|
117
140
|
}
|
|
118
141
|
export interface EditorSelectionPickedMessage {
|
|
119
142
|
/** Pointer-down on an entity in the canvas — host should update selection. */
|
|
@@ -19,9 +19,11 @@ export function spawnEntity(ctx, entity) {
|
|
|
19
19
|
go = createCodeRendered(ctx, entity);
|
|
20
20
|
break;
|
|
21
21
|
case 'tilemap':
|
|
22
|
-
|
|
22
|
+
go = createTilemapStub(ctx, entity);
|
|
23
|
+
break;
|
|
23
24
|
case 'trigger':
|
|
24
|
-
|
|
25
|
+
go = createTriggerStub(ctx, entity);
|
|
26
|
+
break;
|
|
25
27
|
default: {
|
|
26
28
|
const exhaustive = entity;
|
|
27
29
|
throw new Error(`[unboxy/scene] unknown entity kind: ${JSON.stringify(exhaustive)}`);
|
|
@@ -122,6 +124,61 @@ function tagGameObject(go, entity) {
|
|
|
122
124
|
if (entity.properties)
|
|
123
125
|
go.setData('entityProperties', entity.properties);
|
|
124
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Trigger stub renderer — slice 3. Renders the trigger zone as a
|
|
129
|
+
* semi-transparent fill with cyan tint per design 03 §8.2 (in edit mode it
|
|
130
|
+
* is always visible; at play time the SDK will hide it once slice 5 wires
|
|
131
|
+
* the behavior layer).
|
|
132
|
+
*/
|
|
133
|
+
function createTriggerStub(ctx, entity) {
|
|
134
|
+
const TINT = 0x00bfff; // cyan-ish "neutral trigger" per design
|
|
135
|
+
const FILL_ALPHA = 0.25;
|
|
136
|
+
const STROKE_ALPHA = 0.7;
|
|
137
|
+
if (entity.shape.kind === 'rect') {
|
|
138
|
+
const r = ctx.scene.add.rectangle(0, 0, entity.shape.width, entity.shape.height, TINT, FILL_ALPHA);
|
|
139
|
+
r.setStrokeStyle(1.5, TINT, STROKE_ALPHA);
|
|
140
|
+
return r;
|
|
141
|
+
}
|
|
142
|
+
// circle
|
|
143
|
+
const c = ctx.scene.add.circle(0, 0, entity.shape.radius, TINT, FILL_ALPHA);
|
|
144
|
+
c.setStrokeStyle(1.5, TINT, STROKE_ALPHA);
|
|
145
|
+
return c;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Tilemap stub renderer — slice 3. Draws a translucent grid sketch of the
|
|
149
|
+
* tilemap's bounds + cell lines so the user can see where the tilemap is
|
|
150
|
+
* and how big it is. Real tilemap rendering (with tilesets, autotile,
|
|
151
|
+
* Y-sort, etc.) lands in slice 6.
|
|
152
|
+
*/
|
|
153
|
+
function createTilemapStub(ctx, entity) {
|
|
154
|
+
const g = ctx.scene.add.graphics();
|
|
155
|
+
const w = entity.size.width * entity.tileSize.width;
|
|
156
|
+
const h = entity.size.height * entity.tileSize.height;
|
|
157
|
+
g.fillStyle(0xffffff, 0.04);
|
|
158
|
+
g.fillRect(0, 0, w, h);
|
|
159
|
+
// Outer border
|
|
160
|
+
g.lineStyle(2, 0xaaaaaa, 0.6);
|
|
161
|
+
g.strokeRect(0, 0, w, h);
|
|
162
|
+
// Cell grid — only draw if cells aren't too tiny (perf cap).
|
|
163
|
+
if (entity.tileSize.width >= 8 && entity.size.width * entity.size.height < 4000) {
|
|
164
|
+
g.lineStyle(1, 0xaaaaaa, 0.15);
|
|
165
|
+
for (let i = 1; i < entity.size.width; i++) {
|
|
166
|
+
const x = i * entity.tileSize.width;
|
|
167
|
+
g.beginPath();
|
|
168
|
+
g.moveTo(x, 0);
|
|
169
|
+
g.lineTo(x, h);
|
|
170
|
+
g.strokePath();
|
|
171
|
+
}
|
|
172
|
+
for (let i = 1; i < entity.size.height; i++) {
|
|
173
|
+
const y = i * entity.tileSize.height;
|
|
174
|
+
g.beginPath();
|
|
175
|
+
g.moveTo(0, y);
|
|
176
|
+
g.lineTo(w, y);
|
|
177
|
+
g.strokePath();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return g;
|
|
181
|
+
}
|
|
125
182
|
/**
|
|
126
183
|
* Accepts `'#rrggbb'`, `'rrggbb'`, or `'0xrrggbb'` and returns a number
|
|
127
184
|
* suitable for Phaser's color APIs.
|
package/dist/scene/types.d.ts
CHANGED
|
@@ -149,19 +149,59 @@ export interface GroupEntity extends WorldEntityBase {
|
|
|
149
149
|
children: NonGroupWorldEntity[];
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
153
|
-
* tilemap
|
|
154
|
-
* on these kinds so an unmigrated game can't silently render nothing.
|
|
152
|
+
* Tilemap entity — slice 3 ships a stub renderer (placeholder rectangle).
|
|
153
|
+
* Full tilemap painting + autotile lands in slice 6 (06-tilemap.md).
|
|
155
154
|
*/
|
|
156
155
|
export interface TilemapEntity extends WorldEntityBase {
|
|
157
156
|
kind: 'tilemap';
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
tileSize: {
|
|
158
|
+
width: number;
|
|
159
|
+
height: number;
|
|
160
|
+
};
|
|
161
|
+
size: {
|
|
162
|
+
width: number;
|
|
163
|
+
height: number;
|
|
164
|
+
};
|
|
165
|
+
layers: Array<{
|
|
166
|
+
id: string;
|
|
167
|
+
name?: string;
|
|
168
|
+
tilesetIds?: string[];
|
|
169
|
+
z?: number;
|
|
170
|
+
/** 2D array of tile ids; null cells == empty. */
|
|
171
|
+
data?: Array<Array<string | null>>;
|
|
172
|
+
visible?: boolean;
|
|
173
|
+
locked?: boolean;
|
|
174
|
+
autotile?: {
|
|
175
|
+
kind: 'wang-4bit';
|
|
176
|
+
rules?: 'auto-detected' | unknown;
|
|
177
|
+
};
|
|
178
|
+
ySort?: boolean;
|
|
179
|
+
}>;
|
|
160
180
|
}
|
|
181
|
+
export type TriggerShape = {
|
|
182
|
+
kind: 'rect';
|
|
183
|
+
width: number;
|
|
184
|
+
height: number;
|
|
185
|
+
} | {
|
|
186
|
+
kind: 'circle';
|
|
187
|
+
radius: number;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Trigger entity — slice 3 ships a stub renderer (semi-transparent rect/circle
|
|
191
|
+
* with the design's cyan tint). Full behavior wiring (description / targets /
|
|
192
|
+
* behavior script) lands in slice 5 (03-world-editor.md §8).
|
|
193
|
+
*/
|
|
161
194
|
export interface TriggerEntity extends WorldEntityBase {
|
|
162
195
|
kind: 'trigger';
|
|
163
|
-
|
|
164
|
-
|
|
196
|
+
shape: TriggerShape;
|
|
197
|
+
/** User-facing label shown in Hierarchy + on canvas. */
|
|
198
|
+
description?: string;
|
|
199
|
+
/** Entity ids this trigger affects. Slice 5 wires the relationship UI. */
|
|
200
|
+
targets?: string[];
|
|
201
|
+
/** Optional path to behavior script. Slice 5 fills this in. */
|
|
202
|
+
behaviorScript?: string;
|
|
203
|
+
/** Optional preset name (open-door / damage-zone / scene-transition / spawner / heal). */
|
|
204
|
+
preset?: string;
|
|
165
205
|
}
|
|
166
206
|
export type NonGroupWorldEntity = SpriteEntity | PrimitiveEntity | CodeRenderedEntity | TilemapEntity | TriggerEntity;
|
|
167
207
|
export type WorldEntity = NonGroupWorldEntity | GroupEntity;
|