@umicat/phaser-sdk 1.0.18 → 1.0.20

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.
@@ -79,7 +79,7 @@ function handleMessage(game, msg) {
79
79
  applyPanZoomToWorld(game, msg);
80
80
  break;
81
81
  case 'umicat:editor:createEntity':
82
- void createEntity(game, msg.entity, msg.manifestAsset);
82
+ void createEntity(game, msg.entity, msg.manifestAsset, msg.tilemapFile);
83
83
  break;
84
84
  case 'umicat:editor:deleteEntity':
85
85
  deleteEntity(game, msg.entityId);
@@ -1206,7 +1206,7 @@ function applyVisualPatch(go, v) {
1206
1206
  * the host would have to coordinate a build before showing a dropped
1207
1207
  * sprite, which defeats the point of drag-to-place.
1208
1208
  */
1209
- async function createEntity(game, entity, manifestAsset) {
1209
+ async function createEntity(game, entity, manifestAsset, tilemapFile) {
1210
1210
  // HUD mode dispatches to the HUD-runtime spawner (slice 5). The host
1211
1211
  // sends HudEntity records (not WorldEntity), so we type-erase.
1212
1212
  if (getEditorMode(game) === 'hud') {
@@ -1288,13 +1288,26 @@ async function createEntity(game, entity, manifestAsset) {
1288
1288
  // dropped tilemap renders LIVE without a save+reload round-trip.
1289
1289
  const tilemapId = entity.tilemapId;
1290
1290
  if (tilemapId) {
1291
- try {
1292
- await loadTilemapFileIntoScene(sceneRef, tilemapId);
1291
+ const key = tilemapFileCacheKey(tilemapId);
1292
+ // Prefer the host-supplied file CONTENT (the workspace truth). The dist
1293
+ // copy that `loadTilemapFileIntoScene` fetches is STALE until the next
1294
+ // game build, so a freshly-authored/edited tilemap 404s there → magenta
1295
+ // placeholder until save+rebuild. Injecting the passed content renders it
1296
+ // LIVE. Fall back to the dist fetch when the host didn't supply it.
1297
+ if (tilemapFile) {
1298
+ if (sceneRef.cache.json.exists(key))
1299
+ sceneRef.cache.json.remove(key);
1300
+ sceneRef.cache.json.add(key, tilemapFile);
1293
1301
  }
1294
- catch (e) {
1295
- console.warn('[umicat/editor] createEntity: tilemap-ref file load failed:', e);
1302
+ else {
1303
+ try {
1304
+ await loadTilemapFileIntoScene(sceneRef, tilemapId);
1305
+ }
1306
+ catch (e) {
1307
+ console.warn('[umicat/editor] createEntity: tilemap-ref file load failed:', e);
1308
+ }
1296
1309
  }
1297
- const file = sceneRef.cache.json.get(tilemapFileCacheKey(tilemapId));
1310
+ const file = sceneRef.cache.json.get(key);
1298
1311
  for (const layer of file?.layers ?? []) {
1299
1312
  for (const tid of layer.tilesetIds ?? []) {
1300
1313
  queueIfMissing(resolveById(tid));
@@ -38,6 +38,10 @@ const WORLD_BOUNDS_ALPHA = 0.7;
38
38
  const CAMERA_VIEWPORT_COLOR = 0x4488ee;
39
39
  const CAMERA_VIEWPORT_ALPHA = 0.9;
40
40
  const CAMERA_VIEWPORT_LINE_WIDTH = 2;
41
+ // HUD edit mode — the PLAYER'S SCREEN frame (full game-intrinsic resolution).
42
+ // Brighter/distinct from the world-camera rect: it's the fixed boundary HUD
43
+ // widgets anchor to (Godot's UI viewport rect), not the movable camera view.
44
+ const HUD_SCREEN_COLOR = 0x5bc0ff;
41
45
  // FB.10 polish — CSS cursor name per tilemap resize handle. Corner handles
42
46
  // get the diagonal arrows (`nwse-resize` ↖↘ for NW/SE, `nesw-resize` ↗↙ for
43
47
  // NE/SW). Edge handles get the axis arrows (ns / ew). Same conventions as
@@ -1774,10 +1778,28 @@ export class EditorOverlayScene extends Phaser.Scene {
1774
1778
  const snap = this.game['__unboxyEditorScaleSnapshot'];
1775
1779
  const intrinsicW = snap?.gameWidth ?? this.game.scale.width;
1776
1780
  const intrinsicH = snap?.gameHeight ?? this.game.scale.height;
1777
- const camW = intrinsicW / worldSceneCam.zoom;
1778
- const camH = intrinsicH / worldSceneCam.zoom;
1779
- this.graphics.lineStyle(CAMERA_VIEWPORT_LINE_WIDTH, CAMERA_VIEWPORT_COLOR, CAMERA_VIEWPORT_ALPHA);
1780
- this.graphics.strokeRect(camX, camY, camW, camH);
1781
+ if (getEditorMode(this.game) === 'hud') {
1782
+ // HUD mode draw the PLAYER'S SCREEN frame at FULL intrinsic size
1783
+ // (1280×720), NOT intrinsic/zoom. HUD widgets are screen-anchored:
1784
+ // `syncHudCameraToEditorCam` maps HUD-intrinsic (0..1280, 0..720) onto
1785
+ // the full-intrinsic span at (gameCam.scroll). The world-camera zoom
1786
+ // shrinks how much WORLD fits, NOT the screen — so drawing the frame at
1787
+ // intrinsic/zoom (the world-camera viewport, as world mode does) made
1788
+ // the frame 3× smaller than the HUD, and widgets sprawled OUTSIDE it
1789
+ // (the "I can't tell where to place HUD" bug). At full intrinsic the
1790
+ // frame is exactly the HUD's bounds → a stable boundary to anchor to.
1791
+ this.graphics.lineStyle(CAMERA_VIEWPORT_LINE_WIDTH + 1, HUD_SCREEN_COLOR, 1);
1792
+ this.graphics.strokeRect(camX, camY, intrinsicW, intrinsicH);
1793
+ }
1794
+ else {
1795
+ // World mode → the camera viewport rect: where the runtime camera
1796
+ // looks + how much it sees (intrinsic/zoom). Small inside a larger
1797
+ // world for farming/RPG; coincides with world bounds for Tetris etc.
1798
+ const camW = intrinsicW / worldSceneCam.zoom;
1799
+ const camH = intrinsicH / worldSceneCam.zoom;
1800
+ this.graphics.lineStyle(CAMERA_VIEWPORT_LINE_WIDTH, CAMERA_VIEWPORT_COLOR, CAMERA_VIEWPORT_ALPHA);
1801
+ this.graphics.strokeRect(camX, camY, camW, camH);
1802
+ }
1781
1803
  }
1782
1804
  const selectedId = getSelection(this.game);
1783
1805
  if (selectedId) {
@@ -194,6 +194,15 @@ export interface EditorCreateEntityMessage {
194
194
  entity: unknown;
195
195
  /** Asset record to add to runtime cache before spawn. Omit if assetId is already loaded. */
196
196
  manifestAsset?: unknown;
197
+ /**
198
+ * For a `tilemap-ref`: the standalone tilemap file's CONTENT (the parsed
199
+ * `public/tilemaps/{id}.json`). The host fetches it from the WORKSPACE
200
+ * (current truth) and passes it so the SDK injects it into the JSON cache
201
+ * directly. Without this the SDK fetches the file from the deployed dist,
202
+ * which is STALE until the next game build — so a freshly-authored/edited
203
+ * tilemap renders as the magenta placeholder until save+reload.
204
+ */
205
+ tilemapFile?: unknown;
197
206
  }
198
207
  export interface EditorDeleteEntityMessage {
199
208
  type: 'umicat:editor:deleteEntity';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umicat/phaser-sdk",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Umicat Phaser 3 SDK — game infrastructure for the Umicat platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",