@umicat/phaser-sdk 1.0.3 → 1.0.4
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.
|
@@ -89,6 +89,8 @@ export declare class EditorOverlayScene extends Phaser.Scene {
|
|
|
89
89
|
private panStartScreen;
|
|
90
90
|
private panStartScroll;
|
|
91
91
|
private wheelHandler?;
|
|
92
|
+
private snapCandidates;
|
|
93
|
+
private snapGuides;
|
|
92
94
|
constructor();
|
|
93
95
|
init(data: EditorOverlayInitData): void;
|
|
94
96
|
create(): void;
|
|
@@ -268,6 +270,16 @@ export declare class EditorOverlayScene extends Phaser.Scene {
|
|
|
268
270
|
* normal editTilemap dispatch (which mutates the live runtime).
|
|
269
271
|
*/
|
|
270
272
|
private commitTilemapResize;
|
|
273
|
+
/** Candidate alignment lines: world-bounds edges/centers + every OTHER entity's. */
|
|
274
|
+
private buildSnapCandidates;
|
|
275
|
+
/**
|
|
276
|
+
* Snap a candidate position against the cached lines. Returns the
|
|
277
|
+
* adjusted position (or null when nothing is within threshold) and
|
|
278
|
+
* records the matched guide lines for update() to draw.
|
|
279
|
+
*/
|
|
280
|
+
private applySnap;
|
|
281
|
+
/** Pink alignment guide lines across the visible viewport while snapped. */
|
|
282
|
+
private drawSnapGuides;
|
|
271
283
|
/**
|
|
272
284
|
* Selected rect entity's bounds + 8 handle positions in world coords.
|
|
273
285
|
* Returns null when the selection isn't a rect, in HUD mode, or when the
|
|
@@ -72,6 +72,12 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
72
72
|
this.spaceHeld = false;
|
|
73
73
|
this.panStartScreen = null;
|
|
74
74
|
this.panStartScroll = null;
|
|
75
|
+
// Snap-to-align (2026-06-11) — Figma-style magnetic alignment while
|
|
76
|
+
// dragging an entity. Candidates (world-bounds edges/centers + every
|
|
77
|
+
// other entity's edges/centers) are cached once at drag start; guides
|
|
78
|
+
// are the alignment lines currently snapped to, drawn in update().
|
|
79
|
+
this.snapCandidates = null;
|
|
80
|
+
this.snapGuides = { xs: [], ys: [] };
|
|
75
81
|
this.handleShortcut = (e) => {
|
|
76
82
|
// Don't swallow keys when the user is typing in a real form control —
|
|
77
83
|
// shouldn't happen inside the iframe, but be defensive.
|
|
@@ -303,6 +309,9 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
303
309
|
else {
|
|
304
310
|
const target = hit;
|
|
305
311
|
startDrag(this.game, entityId, { x: px, y: py }, { x: target.x, y: target.y });
|
|
312
|
+
// Cache snap candidates once per drag — other entities don't move
|
|
313
|
+
// while we drag this one, so a per-move registry walk would be waste.
|
|
314
|
+
this.buildSnapCandidates(entityId);
|
|
306
315
|
}
|
|
307
316
|
}
|
|
308
317
|
handlePointerMove(pointer) {
|
|
@@ -377,8 +386,23 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
377
386
|
go.__lastDragDy = dy;
|
|
378
387
|
}
|
|
379
388
|
else {
|
|
380
|
-
|
|
381
|
-
|
|
389
|
+
let nx = drag.startEntity.x + dx;
|
|
390
|
+
let ny = drag.startEntity.y + dy;
|
|
391
|
+
// Snap-to-align — Cmd/Ctrl held disables it (Figma convention:
|
|
392
|
+
// precision placement override).
|
|
393
|
+
const ev = pointer.event;
|
|
394
|
+
if (ev?.metaKey || ev?.ctrlKey) {
|
|
395
|
+
this.snapGuides = { xs: [], ys: [] };
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const snapped = this.applySnap(go, nx, ny);
|
|
399
|
+
if (snapped) {
|
|
400
|
+
nx = snapped.x;
|
|
401
|
+
ny = snapped.y;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
go.x = nx;
|
|
405
|
+
go.y = ny;
|
|
382
406
|
}
|
|
383
407
|
}
|
|
384
408
|
handlePointerUp(pointer) {
|
|
@@ -410,11 +434,22 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
410
434
|
const drag = getDrag(this.game);
|
|
411
435
|
if (!drag)
|
|
412
436
|
return;
|
|
437
|
+
// Snap state is per-drag.
|
|
438
|
+
this.snapCandidates = null;
|
|
439
|
+
this.snapGuides = { xs: [], ys: [] };
|
|
413
440
|
const { x: px, y: py } = this.pointerCoords(pointer);
|
|
414
441
|
const dx = px - drag.startWorld.x;
|
|
415
442
|
const dy = py - drag.startWorld.y;
|
|
416
443
|
const before = drag.startEntity;
|
|
417
|
-
|
|
444
|
+
let after = { x: drag.startEntity.x + dx, y: drag.startEntity.y + dy };
|
|
445
|
+
// World mode: report the GameObject's ACTUAL final position, not the
|
|
446
|
+
// raw pointer delta — snapping may have adjusted it during the drag,
|
|
447
|
+
// and the raw-delta value would make the entity "un-snap" on save.
|
|
448
|
+
if (getEditorMode(this.game) !== 'hud') {
|
|
449
|
+
const go = this.findEntityRegistry()?.byId(drag.entityId);
|
|
450
|
+
if (go)
|
|
451
|
+
after = { x: go.x, y: go.y };
|
|
452
|
+
}
|
|
418
453
|
// Reset the per-drag delta accumulator on the GO if HUD mode left one.
|
|
419
454
|
if (getEditorMode(this.game) === 'hud') {
|
|
420
455
|
const reg = this.findEntityRegistry();
|
|
@@ -1389,6 +1424,101 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
1389
1424
|
window.parent.postMessage(message, '*');
|
|
1390
1425
|
}
|
|
1391
1426
|
}
|
|
1427
|
+
// --- Snap-to-align (2026-06-11) -------------------------------------------
|
|
1428
|
+
//
|
|
1429
|
+
// Figma-style magnetic alignment while dragging. The dragged entity's
|
|
1430
|
+
// left/center/right (and top/center/bottom) lines are tested against the
|
|
1431
|
+
// cached candidate lines; when one comes within ~8 screen px the position
|
|
1432
|
+
// snaps to it and a pink guide line renders across the viewport. X and Y
|
|
1433
|
+
// snap independently. Cmd/Ctrl held during the drag bypasses snapping.
|
|
1434
|
+
/** Candidate alignment lines: world-bounds edges/centers + every OTHER entity's. */
|
|
1435
|
+
buildSnapCandidates(draggedId) {
|
|
1436
|
+
if (getEditorMode(this.game) === 'hud') {
|
|
1437
|
+
this.snapCandidates = null;
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const xs = [];
|
|
1441
|
+
const ys = [];
|
|
1442
|
+
if (this.worldBounds) {
|
|
1443
|
+
const wb = this.worldBounds;
|
|
1444
|
+
xs.push(wb.x, wb.x + wb.width / 2, wb.x + wb.width);
|
|
1445
|
+
ys.push(wb.y, wb.y + wb.height / 2, wb.y + wb.height);
|
|
1446
|
+
}
|
|
1447
|
+
const registry = this.findEntityRegistry();
|
|
1448
|
+
if (registry) {
|
|
1449
|
+
for (const go of registry.all()) {
|
|
1450
|
+
if (go.getData('entityId') === draggedId)
|
|
1451
|
+
continue;
|
|
1452
|
+
const b = computeBounds(go);
|
|
1453
|
+
if (!b || (b.width === 0 && b.height === 0))
|
|
1454
|
+
continue;
|
|
1455
|
+
xs.push(b.x, b.x + b.width / 2, b.x + b.width);
|
|
1456
|
+
ys.push(b.y, b.y + b.height / 2, b.y + b.height);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
this.snapCandidates = { xs, ys };
|
|
1460
|
+
this.snapGuides = { xs: [], ys: [] };
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Snap a candidate position against the cached lines. Returns the
|
|
1464
|
+
* adjusted position (or null when nothing is within threshold) and
|
|
1465
|
+
* records the matched guide lines for update() to draw.
|
|
1466
|
+
*/
|
|
1467
|
+
applySnap(go, rawX, rawY) {
|
|
1468
|
+
this.snapGuides = { xs: [], ys: [] };
|
|
1469
|
+
const cands = this.snapCandidates;
|
|
1470
|
+
if (!cands)
|
|
1471
|
+
return null;
|
|
1472
|
+
const b = computeBounds(go);
|
|
1473
|
+
if (!b)
|
|
1474
|
+
return null;
|
|
1475
|
+
const pos = go;
|
|
1476
|
+
// Bounds offset relative to the GO's transform position is constant
|
|
1477
|
+
// during the drag — derive once from the current frame's bounds.
|
|
1478
|
+
const offL = b.x - pos.x;
|
|
1479
|
+
const offT = b.y - pos.y;
|
|
1480
|
+
const cam = this.findActiveEditorCamera();
|
|
1481
|
+
const zoom = cam?.zoom ?? 1;
|
|
1482
|
+
const threshold = 8 / zoom; // 8 screen px regardless of zoom
|
|
1483
|
+
const snapAxis = (raw, off, size, lines) => {
|
|
1484
|
+
const edges = [raw + off, raw + off + size / 2, raw + off + size];
|
|
1485
|
+
let best = null;
|
|
1486
|
+
for (const line of lines) {
|
|
1487
|
+
for (const e of edges) {
|
|
1488
|
+
const d = Math.abs(e - line);
|
|
1489
|
+
if (d <= threshold && (!best || d < best.d)) {
|
|
1490
|
+
best = { pos: raw + (line - e), guide: line, d };
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return best ? { pos: best.pos, guide: best.guide } : null;
|
|
1495
|
+
};
|
|
1496
|
+
const sx = snapAxis(rawX, offL, b.width, cands.xs);
|
|
1497
|
+
const sy = snapAxis(rawY, offT, b.height, cands.ys);
|
|
1498
|
+
if (sx)
|
|
1499
|
+
this.snapGuides.xs.push(sx.guide);
|
|
1500
|
+
if (sy)
|
|
1501
|
+
this.snapGuides.ys.push(sy.guide);
|
|
1502
|
+
if (!sx && !sy)
|
|
1503
|
+
return null;
|
|
1504
|
+
return { x: sx?.pos ?? rawX, y: sy?.pos ?? rawY };
|
|
1505
|
+
}
|
|
1506
|
+
/** Pink alignment guide lines across the visible viewport while snapped. */
|
|
1507
|
+
drawSnapGuides() {
|
|
1508
|
+
if (this.snapGuides.xs.length === 0 && this.snapGuides.ys.length === 0)
|
|
1509
|
+
return;
|
|
1510
|
+
const cam = this.findActiveEditorCamera();
|
|
1511
|
+
if (!cam)
|
|
1512
|
+
return;
|
|
1513
|
+
const view = cam.worldView;
|
|
1514
|
+
this.graphics.lineStyle(1 / cam.zoom, 0xff2d78, 0.9);
|
|
1515
|
+
for (const x of this.snapGuides.xs) {
|
|
1516
|
+
this.graphics.lineBetween(x, view.y, x, view.y + view.height);
|
|
1517
|
+
}
|
|
1518
|
+
for (const y of this.snapGuides.ys) {
|
|
1519
|
+
this.graphics.lineBetween(view.x, y, view.x + view.width, y);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1392
1522
|
// --- Rect-entity resize-handle drag (2026-06-10) --------------------------
|
|
1393
1523
|
//
|
|
1394
1524
|
// Same 8-handle affordance as the tilemap resize above, generalized to
|
|
@@ -1707,6 +1837,8 @@ export class EditorOverlayScene extends Phaser.Scene {
|
|
|
1707
1837
|
// Rect-entity resize handles — render whenever a rect entity is
|
|
1708
1838
|
// selected (2026-06-10).
|
|
1709
1839
|
this.drawEntityResizeOverlay();
|
|
1840
|
+
// Snap-to-align guide lines — render while a drag is snapped (2026-06-11).
|
|
1841
|
+
this.drawSnapGuides();
|
|
1710
1842
|
}
|
|
1711
1843
|
/**
|
|
1712
1844
|
* Render the 8 resize handles (when paint mode is active) and the
|