@combos-fun/plugin-development-tool 0.0.47 → 0.0.49
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/README.md +11 -1
- package/agent-skill.md +41 -27
- package/combos-plugin.json +3 -4
- package/dist/plugin-development-tool.cjs.js +673 -432
- package/dist/plugin-development-tool.cjs.js.map +1 -1
- package/dist/plugin-development-tool.cjs.prod.js +1 -1
- package/dist/plugin-development-tool.d.ts +53 -69
- package/dist/plugin-development-tool.esm.js +674 -434
- package/dist/plugin-development-tool.esm.js.map +1 -1
- package/dist/vite.cjs +540 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.ts +43 -0
- package/dist/vite.mjs +527 -0
- package/dist/vite.mjs.map +1 -0
- package/package.json +11 -8
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
|
-
import { Component, System, OBSERVER_TYPE,
|
|
3
|
-
import { RendererSystem } from '@combos-fun/plugin-renderer';
|
|
4
|
-
import { Graphics } from '@combos-fun/plugin-renderer-graphics';
|
|
5
|
-
import { Event, HIT_AREA_TYPE } from '@combos-fun/plugin-renderer-event';
|
|
2
|
+
import { Component, System, OBSERVER_TYPE, decorators } from '@combos-fun/engine';
|
|
6
3
|
import { IDE_PROPERTY_METADATA } from '@combos-fun/inspector-decorator';
|
|
7
4
|
|
|
8
5
|
/** Parent → iframe (also `window` CustomEvent / `game.emit`): toggle pick mode. Payload: `{ enabled: boolean }`. */
|
|
@@ -22,10 +19,8 @@ const COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY = 'combos-development-tool:apply-pr
|
|
|
22
19
|
/** iframe → parent: game scene bootstrap finished; parent may enable pick mode. */
|
|
23
20
|
const COMBOS_DEVELOPMENT_TOOL_READY = 'combos-development-tool:ready';
|
|
24
21
|
/**
|
|
25
|
-
* Parent → iframe: mute or unmute
|
|
26
|
-
*
|
|
27
|
-
* Note: play / pause / resume are owned by the engine core lifecycle protocol
|
|
28
|
-
* (`combos-game:set-playing` / `combos-game:state-changed`), not this tool.
|
|
22
|
+
* Parent → iframe: mute or unmute. Handled by `@combos-fun/plugin-sound`.
|
|
23
|
+
* Payload: `{ muted: boolean }`.
|
|
29
24
|
*/
|
|
30
25
|
const COMBOS_DEVELOPMENT_TOOL_SET_MUTED = 'combos-development-tool:set-muted';
|
|
31
26
|
/** iframe → parent (and `game.emit`): current mute snapshot after a successful change. */
|
|
@@ -45,8 +40,7 @@ const COMBOS_DEVELOPMENT_TOOL_MARKER_OVERLAY_SUCCESS = 'combos-development-tool:
|
|
|
45
40
|
|
|
46
41
|
/**
|
|
47
42
|
* Marks a `GameObject` as selectable in editor pick mode. While the development tool is enabled,
|
|
48
|
-
* only nodes carrying this component
|
|
49
|
-
* are removed and cached until disable.
|
|
43
|
+
* only nodes carrying this component are hit-tested on the canvas overlay.
|
|
50
44
|
*/
|
|
51
45
|
class CombosDevelopmentToolTarget extends Component {
|
|
52
46
|
constructor() {
|
|
@@ -59,6 +53,69 @@ class CombosDevelopmentToolTarget extends Component {
|
|
|
59
53
|
}
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
function cssRgba(color, alpha = 1) {
|
|
57
|
+
const r = (color >> 16) & 0xff;
|
|
58
|
+
const g = (color >> 8) & 0xff;
|
|
59
|
+
const b = color & 0xff;
|
|
60
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
61
|
+
}
|
|
62
|
+
/** Pixi-v8-style path builder backed by `CanvasRenderingContext2D`. */
|
|
63
|
+
class CanvasMarkerGraphics {
|
|
64
|
+
constructor(ctx) {
|
|
65
|
+
this.ctx = ctx;
|
|
66
|
+
}
|
|
67
|
+
clear() {
|
|
68
|
+
this.ctx.beginPath();
|
|
69
|
+
}
|
|
70
|
+
roundRect(x, y, width, height, radius = 0) {
|
|
71
|
+
const ctx = this.ctx;
|
|
72
|
+
if (typeof ctx.roundRect === 'function') {
|
|
73
|
+
ctx.roundRect(x, y, width, height, radius);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const r = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
|
|
77
|
+
ctx.moveTo(x + r, y);
|
|
78
|
+
ctx.lineTo(x + width - r, y);
|
|
79
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + r);
|
|
80
|
+
ctx.lineTo(x + width, y + height - r);
|
|
81
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
|
|
82
|
+
ctx.lineTo(x + r, y + height);
|
|
83
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - r);
|
|
84
|
+
ctx.lineTo(x, y + r);
|
|
85
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
86
|
+
ctx.closePath();
|
|
87
|
+
}
|
|
88
|
+
rect(x, y, width, height) {
|
|
89
|
+
this.ctx.rect(x, y, width, height);
|
|
90
|
+
}
|
|
91
|
+
circle(x, y, radius) {
|
|
92
|
+
this.ctx.moveTo(x + radius, y);
|
|
93
|
+
this.ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
94
|
+
}
|
|
95
|
+
poly(points, close = true) {
|
|
96
|
+
if (points.length < 4)
|
|
97
|
+
return;
|
|
98
|
+
this.ctx.moveTo(points[0], points[1]);
|
|
99
|
+
for (let i = 2; i < points.length; i += 2) {
|
|
100
|
+
this.ctx.lineTo(points[i], points[i + 1]);
|
|
101
|
+
}
|
|
102
|
+
if (close) {
|
|
103
|
+
this.ctx.closePath();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
fill(style) {
|
|
107
|
+
this.ctx.fillStyle = cssRgba(style.color, style.alpha ?? 1);
|
|
108
|
+
this.ctx.fill();
|
|
109
|
+
this.ctx.beginPath();
|
|
110
|
+
}
|
|
111
|
+
stroke(style) {
|
|
112
|
+
this.ctx.strokeStyle = cssRgba(style.color, style.alpha ?? 1);
|
|
113
|
+
this.ctx.lineWidth = style.width;
|
|
114
|
+
this.ctx.stroke();
|
|
115
|
+
this.ctx.beginPath();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
62
119
|
/**
|
|
63
120
|
* Registered marker kinds, in priority order: a GameObject is marked by the
|
|
64
121
|
* first registered component it owns. For now this is **`Sound` only**; the
|
|
@@ -68,7 +125,7 @@ class CombosDevelopmentToolTarget extends Component {
|
|
|
68
125
|
const MARKER_COMPONENTS = [
|
|
69
126
|
{ componentName: 'Sound', badgeColor: 0x8b5cf6, glyph: 'speaker' },
|
|
70
127
|
];
|
|
71
|
-
/** Fixed badge size in
|
|
128
|
+
/** Fixed badge size in overlay CSS pixels. */
|
|
72
129
|
const MARKER_ICON_SIZE = 28;
|
|
73
130
|
/**
|
|
74
131
|
* First registered marker def whose component is present on `go`, else `null`.
|
|
@@ -112,7 +169,7 @@ function drawDotGlyph(g, s) {
|
|
|
112
169
|
g.circle(s * 0.5, s * 0.5, s * 0.18);
|
|
113
170
|
g.fill({ color: GLYPH_COLOR, alpha: 0.96 });
|
|
114
171
|
}
|
|
115
|
-
/** Render a full marker (badge + glyph) into `g`, sized to `size` (
|
|
172
|
+
/** Render a full marker (badge + glyph) into `g`, sized to `size` (overlay CSS pixels). */
|
|
116
173
|
function drawMarkerIcon(g, def, size = MARKER_ICON_SIZE) {
|
|
117
174
|
drawBadge(g, def, size);
|
|
118
175
|
switch (def.glyph) {
|
|
@@ -194,6 +251,85 @@ function mergeAllowedMessageOrigins(extra) {
|
|
|
194
251
|
return result;
|
|
195
252
|
}
|
|
196
253
|
|
|
254
|
+
const OVERLAY_Z_INDEX = '2147483000';
|
|
255
|
+
const OVERLAY_ATTR = 'data-combos-development-tool-overlay';
|
|
256
|
+
function ensureParent(gameCanvas, overlay) {
|
|
257
|
+
const parent = gameCanvas.parentElement ?? document.body;
|
|
258
|
+
if (overlay.parentElement !== parent) {
|
|
259
|
+
parent.appendChild(overlay);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Transparent HTML canvas stacked on the game canvas.
|
|
264
|
+
* Pick mode sets `pointer-events: auto` so game Event / Event3D do not receive the tap.
|
|
265
|
+
*/
|
|
266
|
+
function createOverlayCanvas() {
|
|
267
|
+
if (typeof document === 'undefined') {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const canvas = document.createElement('canvas');
|
|
271
|
+
canvas.setAttribute(OVERLAY_ATTR, 'true');
|
|
272
|
+
canvas.style.position = 'fixed';
|
|
273
|
+
canvas.style.left = '0';
|
|
274
|
+
canvas.style.top = '0';
|
|
275
|
+
canvas.style.width = '0';
|
|
276
|
+
canvas.style.height = '0';
|
|
277
|
+
canvas.style.margin = '0';
|
|
278
|
+
canvas.style.padding = '0';
|
|
279
|
+
canvas.style.border = '0';
|
|
280
|
+
canvas.style.background = 'transparent';
|
|
281
|
+
canvas.style.pointerEvents = 'none';
|
|
282
|
+
canvas.style.touchAction = 'none';
|
|
283
|
+
canvas.style.userSelect = 'none';
|
|
284
|
+
canvas.style.zIndex = OVERLAY_Z_INDEX;
|
|
285
|
+
canvas.style.display = 'none';
|
|
286
|
+
const ctx = canvas.getContext('2d');
|
|
287
|
+
if (!ctx) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
let pickEnabled = false;
|
|
291
|
+
let visible = false;
|
|
292
|
+
const sync = (gameCanvas) => {
|
|
293
|
+
ensureParent(gameCanvas, canvas);
|
|
294
|
+
const rect = gameCanvas.getBoundingClientRect();
|
|
295
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
296
|
+
canvas.style.display = 'none';
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
canvas.style.display = visible ? 'block' : 'none';
|
|
300
|
+
canvas.style.left = `${rect.left}px`;
|
|
301
|
+
canvas.style.top = `${rect.top}px`;
|
|
302
|
+
canvas.style.width = `${rect.width}px`;
|
|
303
|
+
canvas.style.height = `${rect.height}px`;
|
|
304
|
+
const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
|
|
305
|
+
const pixelW = Math.max(1, Math.round(rect.width * dpr));
|
|
306
|
+
const pixelH = Math.max(1, Math.round(rect.height * dpr));
|
|
307
|
+
if (canvas.width !== pixelW || canvas.height !== pixelH) {
|
|
308
|
+
canvas.width = pixelW;
|
|
309
|
+
canvas.height = pixelH;
|
|
310
|
+
}
|
|
311
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
312
|
+
return { width: rect.width, height: rect.height };
|
|
313
|
+
};
|
|
314
|
+
return {
|
|
315
|
+
canvas,
|
|
316
|
+
ctx,
|
|
317
|
+
sync,
|
|
318
|
+
setPickEnabled(on) {
|
|
319
|
+
pickEnabled = on;
|
|
320
|
+
canvas.style.pointerEvents = pickEnabled ? 'auto' : 'none';
|
|
321
|
+
canvas.style.cursor = pickEnabled ? 'crosshair' : 'default';
|
|
322
|
+
},
|
|
323
|
+
setVisible(on) {
|
|
324
|
+
visible = on;
|
|
325
|
+
canvas.style.display = on ? 'block' : 'none';
|
|
326
|
+
},
|
|
327
|
+
destroy() {
|
|
328
|
+
canvas.remove();
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
197
333
|
function isSceneSourceAnchor(v) {
|
|
198
334
|
return (!!v &&
|
|
199
335
|
typeof v === 'object' &&
|
|
@@ -587,29 +723,310 @@ function isClearSelectionMessage(data) {
|
|
|
587
723
|
return data.type === COMBOS_DEVELOPMENT_TOOL_CLEAR_SELECTION;
|
|
588
724
|
}
|
|
589
725
|
|
|
726
|
+
/** Screen-space pad used when a target has no positive size / projected extent. */
|
|
727
|
+
const DEFAULT_POINT_EXTENT = 32;
|
|
728
|
+
function identityMat() {
|
|
729
|
+
return { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
|
|
730
|
+
}
|
|
731
|
+
function multiplyMat(p, l) {
|
|
732
|
+
return {
|
|
733
|
+
a: p.a * l.a + p.c * l.b,
|
|
734
|
+
b: p.b * l.a + p.d * l.b,
|
|
735
|
+
c: p.a * l.c + p.c * l.d,
|
|
736
|
+
d: p.b * l.c + p.d * l.d,
|
|
737
|
+
tx: p.a * l.tx + p.c * l.ty + p.tx,
|
|
738
|
+
ty: p.b * l.tx + p.d * l.ty + p.ty,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function applyMat(m, x, y) {
|
|
742
|
+
return {
|
|
743
|
+
x: m.a * x + m.c * y + m.tx,
|
|
744
|
+
y: m.b * x + m.d * y + m.ty,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Local matrix matching `plugin-renderer` ContainerManager:
|
|
749
|
+
* position (+ parent size * anchor), rotation, scale, pivot = size * origin.
|
|
750
|
+
*/
|
|
751
|
+
function localTransformMatrix(t) {
|
|
752
|
+
const x = t.position.x + (t.parent ? t.parent.size.width * t.anchor.x : 0);
|
|
753
|
+
const y = t.position.y + (t.parent ? t.parent.size.height * t.anchor.y : 0);
|
|
754
|
+
const pivotX = t.size.width * t.origin.x;
|
|
755
|
+
const pivotY = t.size.height * t.origin.y;
|
|
756
|
+
const cos = Math.cos(t.rotation);
|
|
757
|
+
const sin = Math.sin(t.rotation);
|
|
758
|
+
const a = cos * t.scale.x;
|
|
759
|
+
const b = sin * t.scale.x;
|
|
760
|
+
const c = -sin * t.scale.y;
|
|
761
|
+
const d = cos * t.scale.y;
|
|
762
|
+
return {
|
|
763
|
+
a,
|
|
764
|
+
b,
|
|
765
|
+
c,
|
|
766
|
+
d,
|
|
767
|
+
tx: x - (pivotX * a + pivotY * c),
|
|
768
|
+
ty: y - (pivotX * b + pivotY * d),
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function worldTransformMatrix(t) {
|
|
772
|
+
const chain = [];
|
|
773
|
+
let cur = t;
|
|
774
|
+
while (cur) {
|
|
775
|
+
chain.push(cur);
|
|
776
|
+
cur = cur.parent;
|
|
777
|
+
}
|
|
778
|
+
chain.reverse();
|
|
779
|
+
let m = identityMat();
|
|
780
|
+
for (const node of chain) {
|
|
781
|
+
m = multiplyMat(m, localTransformMatrix(node));
|
|
782
|
+
}
|
|
783
|
+
return m;
|
|
784
|
+
}
|
|
785
|
+
function aabbFromPoints(points) {
|
|
786
|
+
if (points.length === 0)
|
|
787
|
+
return null;
|
|
788
|
+
let minX = Infinity;
|
|
789
|
+
let minY = Infinity;
|
|
790
|
+
let maxX = -Infinity;
|
|
791
|
+
let maxY = -Infinity;
|
|
792
|
+
for (const p of points) {
|
|
793
|
+
if (!Number.isFinite(p.x) || !Number.isFinite(p.y))
|
|
794
|
+
continue;
|
|
795
|
+
minX = Math.min(minX, p.x);
|
|
796
|
+
minY = Math.min(minY, p.y);
|
|
797
|
+
maxX = Math.max(maxX, p.x);
|
|
798
|
+
maxY = Math.max(maxY, p.y);
|
|
799
|
+
}
|
|
800
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY))
|
|
801
|
+
return null;
|
|
802
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
803
|
+
}
|
|
804
|
+
function designToCss(point, design, css) {
|
|
805
|
+
if (!design || design.width <= 0 || design.height <= 0) {
|
|
806
|
+
return { x: point.x, y: point.y };
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
x: point.x * (css.width / design.width),
|
|
810
|
+
y: point.y * (css.height / design.height),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function cssToDesign(point, design, css) {
|
|
814
|
+
if (!design || design.width <= 0 || design.height <= 0 || css.width <= 0 || css.height <= 0) {
|
|
815
|
+
return { x: point.x, y: point.y };
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
x: point.x * (design.width / css.width),
|
|
819
|
+
y: point.y * (design.height / css.height),
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function readPositiveNumber(value) {
|
|
823
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;
|
|
824
|
+
}
|
|
825
|
+
function resolveDesignSize(game) {
|
|
826
|
+
const renderer = game?.getSystem?.('Renderer');
|
|
827
|
+
const screen = renderer?.application?.screen;
|
|
828
|
+
const sw = readPositiveNumber(screen?.width);
|
|
829
|
+
const sh = readPositiveNumber(screen?.height);
|
|
830
|
+
if (sw && sh) {
|
|
831
|
+
return { width: sw, height: sh };
|
|
832
|
+
}
|
|
833
|
+
const pw = readPositiveNumber(renderer?.params?.width);
|
|
834
|
+
const ph = readPositiveNumber(renderer?.params?.height);
|
|
835
|
+
if (pw && ph) {
|
|
836
|
+
return { width: pw, height: ph };
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
function resolveCamera(game) {
|
|
841
|
+
const renderer = game?.getSystem?.('Renderer3DSystem');
|
|
842
|
+
return renderer?.threeContext?.camera ?? null;
|
|
843
|
+
}
|
|
844
|
+
function resolveObject3D(game, id) {
|
|
845
|
+
const renderer = game?.getSystem?.('Renderer3DSystem');
|
|
846
|
+
return renderer?.threeContext?.nodes?.get?.(id) ?? null;
|
|
847
|
+
}
|
|
848
|
+
function asTransform3D(value) {
|
|
849
|
+
if (!value || typeof value !== 'object')
|
|
850
|
+
return null;
|
|
851
|
+
const t = value;
|
|
852
|
+
const hasWorld = [t.worldPositionX, t.worldPositionY, t.worldPositionZ].some(v => typeof v === 'number' && Number.isFinite(v));
|
|
853
|
+
const hasLocal = [t.positionX, t.positionY, t.positionZ].some(v => typeof v === 'number' && Number.isFinite(v));
|
|
854
|
+
return hasWorld || hasLocal ? t : null;
|
|
855
|
+
}
|
|
856
|
+
function multiplyMat4Vec4(m, x, y, z, w) {
|
|
857
|
+
return [
|
|
858
|
+
m[0] * x + m[4] * y + m[8] * z + m[12] * w,
|
|
859
|
+
m[1] * x + m[5] * y + m[9] * z + m[13] * w,
|
|
860
|
+
m[2] * x + m[6] * y + m[10] * z + m[14] * w,
|
|
861
|
+
m[3] * x + m[7] * y + m[11] * z + m[15] * w,
|
|
862
|
+
];
|
|
863
|
+
}
|
|
864
|
+
function projectWorldPoint(x, y, z, camera, css) {
|
|
865
|
+
const view = camera.matrixWorldInverse?.elements;
|
|
866
|
+
const proj = camera.projectionMatrix?.elements;
|
|
867
|
+
if (!view || view.length < 16 || !proj || proj.length < 16) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
if (css.width <= 0 || css.height <= 0) {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
const viewed = multiplyMat4Vec4(view, x, y, z, 1);
|
|
874
|
+
const clip = multiplyMat4Vec4(proj, viewed[0], viewed[1], viewed[2], viewed[3]);
|
|
875
|
+
if (!Number.isFinite(clip[3]) || Math.abs(clip[3]) < 1e-8) {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
if (clip[3] < 0) {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
const ndcX = clip[0] / clip[3];
|
|
882
|
+
const ndcY = clip[1] / clip[3];
|
|
883
|
+
const ndcZ = clip[2] / clip[3];
|
|
884
|
+
return {
|
|
885
|
+
x: (ndcX + 1) * 0.5 * css.width,
|
|
886
|
+
y: (1 - ndcY) * 0.5 * css.height,
|
|
887
|
+
depth: ndcZ,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function transformMat4Point(m, x, y, z) {
|
|
891
|
+
const w = m[3] * x + m[7] * y + m[11] * z + m[15];
|
|
892
|
+
const invW = Math.abs(w) < 1e-8 ? 1 : 1 / w;
|
|
893
|
+
return [
|
|
894
|
+
(m[0] * x + m[4] * y + m[8] * z + m[12]) * invW,
|
|
895
|
+
(m[1] * x + m[5] * y + m[9] * z + m[13]) * invW,
|
|
896
|
+
(m[2] * x + m[6] * y + m[10] * z + m[14]) * invW,
|
|
897
|
+
];
|
|
898
|
+
}
|
|
899
|
+
function collectObject3DWorldCorners(node) {
|
|
900
|
+
const corners = [];
|
|
901
|
+
const visit = (obj) => {
|
|
902
|
+
const geo = obj.geometry;
|
|
903
|
+
if (!geo)
|
|
904
|
+
return;
|
|
905
|
+
geo.computeBoundingBox?.();
|
|
906
|
+
const box = geo.boundingBox;
|
|
907
|
+
const m = obj.matrixWorld?.elements;
|
|
908
|
+
if (!box || !m || m.length < 16)
|
|
909
|
+
return;
|
|
910
|
+
const { min, max } = box;
|
|
911
|
+
for (const x of [min.x, max.x]) {
|
|
912
|
+
for (const y of [min.y, max.y]) {
|
|
913
|
+
for (const z of [min.z, max.z]) {
|
|
914
|
+
corners.push(transformMat4Point(m, x, y, z));
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
node.updateWorldMatrix?.(true, true);
|
|
920
|
+
if (typeof node.traverse === 'function') {
|
|
921
|
+
node.traverse(visit);
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
visit(node);
|
|
925
|
+
}
|
|
926
|
+
return corners;
|
|
927
|
+
}
|
|
928
|
+
function padAround(point, extent) {
|
|
929
|
+
const size = Math.max(1, extent);
|
|
930
|
+
return {
|
|
931
|
+
x: point.x - size / 2,
|
|
932
|
+
y: point.y - size / 2,
|
|
933
|
+
width: size,
|
|
934
|
+
height: size,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
function projectTransform2D(transform, css, design) {
|
|
938
|
+
const matrix = worldTransformMatrix(transform);
|
|
939
|
+
const width = transform.size.width;
|
|
940
|
+
const height = transform.size.height;
|
|
941
|
+
const localCorners = width > 0 && height > 0
|
|
942
|
+
? [
|
|
943
|
+
{ x: 0, y: 0 },
|
|
944
|
+
{ x: width, y: 0 },
|
|
945
|
+
{ x: width, y: height },
|
|
946
|
+
{ x: 0, y: height },
|
|
947
|
+
]
|
|
948
|
+
: [{ x: 0, y: 0 }];
|
|
949
|
+
const world = localCorners.map(p => applyMat(matrix, p.x, p.y));
|
|
950
|
+
const screen = world.map(p => designToCss(p, design, css));
|
|
951
|
+
const aabb = aabbFromPoints(screen);
|
|
952
|
+
if (aabb && aabb.width > 0 && aabb.height > 0) {
|
|
953
|
+
return aabb;
|
|
954
|
+
}
|
|
955
|
+
const origin = screen[0] ?? { x: 0, y: 0 };
|
|
956
|
+
return padAround(origin, 1);
|
|
957
|
+
}
|
|
958
|
+
function projectTransform3D(transform, camera, css, object3D) {
|
|
959
|
+
if (object3D) {
|
|
960
|
+
const corners = collectObject3DWorldCorners(object3D);
|
|
961
|
+
const projected = corners
|
|
962
|
+
.map(([x, y, z]) => projectWorldPoint(x, y, z, camera, css))
|
|
963
|
+
.filter((p) => p != null);
|
|
964
|
+
const aabb = aabbFromPoints(projected);
|
|
965
|
+
if (aabb && aabb.width > 0 && aabb.height > 0) {
|
|
966
|
+
return aabb;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const x = transform.worldPositionX ?? transform.positionX ?? 0;
|
|
970
|
+
const y = transform.worldPositionY ?? transform.positionY ?? 0;
|
|
971
|
+
const z = transform.worldPositionZ ?? transform.positionZ ?? 0;
|
|
972
|
+
const projected = projectWorldPoint(x, y, z, camera, css);
|
|
973
|
+
if (!projected)
|
|
974
|
+
return null;
|
|
975
|
+
const scale = Math.max(Math.abs(transform.scaleX ?? 1), Math.abs(transform.scaleY ?? 1), Math.abs(transform.scaleZ ?? 1), 1);
|
|
976
|
+
return padAround(projected, DEFAULT_POINT_EXTENT * scale);
|
|
977
|
+
}
|
|
978
|
+
function projectGameObjectToScreen(go, opts) {
|
|
979
|
+
if (opts.css.width <= 0 || opts.css.height <= 0) {
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
const t3d = asTransform3D(go.getComponent('Transform3D'));
|
|
983
|
+
if (t3d && opts.camera) {
|
|
984
|
+
return projectTransform3D(t3d, opts.camera, opts.css, opts.object3D);
|
|
985
|
+
}
|
|
986
|
+
return projectTransform2D(go.transform, opts.css, opts.design ?? null);
|
|
987
|
+
}
|
|
988
|
+
function rectContains(rect, x, y) {
|
|
989
|
+
return x >= rect.x && y >= rect.y && x <= rect.x + rect.width && y <= rect.y + rect.height;
|
|
990
|
+
}
|
|
991
|
+
/** Smallest containing rect wins; later items win ties (front-most in walk order). */
|
|
992
|
+
function hitTestRects(x, y, items) {
|
|
993
|
+
let best = null;
|
|
994
|
+
for (let i = 0; i < items.length; i++) {
|
|
995
|
+
const item = items[i];
|
|
996
|
+
if (!rectContains(item.rect, x, y))
|
|
997
|
+
continue;
|
|
998
|
+
const area = Math.max(item.rect.width, 0) * Math.max(item.rect.height, 0);
|
|
999
|
+
if (!best || area < best.area || (area === best.area && i > best.index)) {
|
|
1000
|
+
best = { id: item.id, area, index: i };
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return best ? best.id : null;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
590
1006
|
const OUTLINE_GO_NAME = '__combosDevelopmentToolOutline';
|
|
591
1007
|
const MARKER_GO_NAME_PREFIX = '__combosDevelopmentToolMarker:';
|
|
1008
|
+
const OUTLINE_COLOR = '#55ffaa';
|
|
592
1009
|
/**
|
|
593
1010
|
* **Off** at start. While enabled:
|
|
594
|
-
* -
|
|
595
|
-
*
|
|
1011
|
+
* - a transparent HTML canvas overlay captures pointer events (game Event / Event3D
|
|
1012
|
+
* do not receive the tap);
|
|
1013
|
+
* - only nodes with {@link CombosDevelopmentToolTarget} are pickable;
|
|
1014
|
+
* - selection outline and markers are drawn with the Canvas 2D API (no renderer plugin).
|
|
596
1015
|
*
|
|
597
1016
|
* Turn on/off via:
|
|
598
1017
|
* - `window.dispatchEvent(new CustomEvent(COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, { detail: { enabled: true } }))`
|
|
599
1018
|
* - `window.postMessage({ type: COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, enabled: true }, targetOrigin)` (e.g. from parent iframe)
|
|
600
1019
|
* - `game.emit(COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, { enabled: true })` or `getSystem(CombosDevelopmentToolSystem).setEnabled(true)`
|
|
601
1020
|
*
|
|
602
|
-
* Mute
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
* Mute applies when `SoundSystem` is registered; otherwise the desired value is remembered until it appears.
|
|
606
|
-
* Emits / posts `COMBOS_DEVELOPMENT_TOOL_STATE_CHANGED` with `{ muted }`.
|
|
1021
|
+
* Mute is owned by `@combos-fun/plugin-sound` (`combos-development-tool:set-muted` /
|
|
1022
|
+
* `SoundSystem.setMuted`). This system still forwards `.setMuted()` to
|
|
1023
|
+
* `SoundSystem` so older in-game callers keep working.
|
|
607
1024
|
*
|
|
608
1025
|
* Play / pause / resume are handled by the engine core lifecycle protocol
|
|
609
1026
|
* (`combos-game:set-playing` / `combos-game:state-changed`), not this tool.
|
|
610
1027
|
*
|
|
611
1028
|
* While enabled: `game.emit(COMBOS_DEVELOPMENT_TOOL_REFRESH)`, `window` event `combos-development-tool:refresh`,
|
|
612
|
-
* or `postMessage({ type: 'combos-development-tool:refresh' })`
|
|
1029
|
+
* or `postMessage({ type: 'combos-development-tool:refresh' })` after adding/removing objects.
|
|
613
1030
|
*/
|
|
614
1031
|
let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends System {
|
|
615
1032
|
constructor() {
|
|
@@ -617,42 +1034,28 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
617
1034
|
this.postMessageOrigin = '*';
|
|
618
1035
|
this.allowedMessageOrigins = mergeAllowedMessageOrigins();
|
|
619
1036
|
this.enabled = false;
|
|
620
|
-
this.outlineGo = null;
|
|
621
1037
|
this.selected = null;
|
|
622
|
-
this.
|
|
623
|
-
this.
|
|
624
|
-
|
|
625
|
-
this.disabledGameEventGoIds = new Set();
|
|
626
|
-
/** Pick `Event` injected for {@link CombosDevelopmentToolTarget} nodes. */
|
|
627
|
-
this.pickEvents = new Set();
|
|
1038
|
+
this.overlay = null;
|
|
1039
|
+
this.overlayCss = { width: 0, height: 0 };
|
|
1040
|
+
this.pointerDownGoId = null;
|
|
628
1041
|
this.needsRescan = false;
|
|
629
|
-
this.lastOutlineBounds = null;
|
|
630
1042
|
/** Remembered mute when `SoundSystem` is not registered yet. */
|
|
631
1043
|
this.mutedWithoutSoundSystem = false;
|
|
632
1044
|
/**
|
|
633
1045
|
* Marker overlay (see {@link markerLayer}): a visual-only icon layer, toggled
|
|
634
|
-
* independently of pick mode + mute. Selection/persistence
|
|
635
|
-
*
|
|
636
|
-
* (added by the game code), so it is picked and serialized through the normal
|
|
637
|
-
* Target path. The icon just makes the invisible object visible and, by adding
|
|
638
|
-
* to the owner's rendered bounds, gives its Target pick a clickable hit area.
|
|
1046
|
+
* independently of pick mode + mute. Selection/persistence still go through
|
|
1047
|
+
* `CombosDevelopmentToolTarget` (added by the Vite plugin).
|
|
639
1048
|
*/
|
|
640
1049
|
this.markerOverlayEnabled = false;
|
|
641
1050
|
this.needsMarkerRescan = false;
|
|
642
|
-
/** ownerGoId →
|
|
643
|
-
this.
|
|
1051
|
+
/** ownerGoId → marker def currently shown. */
|
|
1052
|
+
this.markerOwners = new Set();
|
|
644
1053
|
this.onWindowSetPickMode = (e) => {
|
|
645
1054
|
const d = e.detail;
|
|
646
1055
|
if (d && typeof d.enabled === 'boolean') {
|
|
647
1056
|
this.setEnabled(d.enabled);
|
|
648
1057
|
}
|
|
649
1058
|
};
|
|
650
|
-
this.onWindowSetMuted = (e) => {
|
|
651
|
-
const d = e.detail;
|
|
652
|
-
if (d && typeof d.muted === 'boolean') {
|
|
653
|
-
this.setMuted(d.muted);
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
1059
|
this.onWindowSetMarkerOverlay = (e) => {
|
|
657
1060
|
const d = e.detail;
|
|
658
1061
|
if (d && typeof d.enabled === 'boolean') {
|
|
@@ -674,9 +1077,6 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
674
1077
|
if (d.type === COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE && typeof d.enabled === 'boolean') {
|
|
675
1078
|
this.setEnabled(d.enabled);
|
|
676
1079
|
}
|
|
677
|
-
else if (d.type === COMBOS_DEVELOPMENT_TOOL_SET_MUTED && typeof d.muted === 'boolean') {
|
|
678
|
-
this.setMuted(d.muted);
|
|
679
|
-
}
|
|
680
1080
|
else if (d.type === COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY &&
|
|
681
1081
|
typeof d.enabled === 'boolean') {
|
|
682
1082
|
this.setMarkerOverlay(d.enabled);
|
|
@@ -699,49 +1099,75 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
699
1099
|
this.onGameRefresh = () => {
|
|
700
1100
|
this.requestSceneRescan();
|
|
701
1101
|
};
|
|
702
|
-
this.onGameSetMuted = (payload) => {
|
|
703
|
-
if (payload && typeof payload.muted === 'boolean') {
|
|
704
|
-
this.setMuted(payload.muted);
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
1102
|
this.onWindowRefresh = () => {
|
|
708
1103
|
this.requestSceneRescan();
|
|
709
1104
|
};
|
|
1105
|
+
this.onPointerDown = (e) => {
|
|
1106
|
+
if (!this.enabled)
|
|
1107
|
+
return;
|
|
1108
|
+
e.preventDefault();
|
|
1109
|
+
e.stopPropagation();
|
|
1110
|
+
try {
|
|
1111
|
+
this.overlay?.canvas.setPointerCapture(e.pointerId);
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
/* ignore */
|
|
1115
|
+
}
|
|
1116
|
+
const hit = this.hitTestPointer(e);
|
|
1117
|
+
this.pointerDownGoId = hit?.id ?? null;
|
|
1118
|
+
};
|
|
1119
|
+
this.onPointerUp = (e) => {
|
|
1120
|
+
if (!this.enabled)
|
|
1121
|
+
return;
|
|
1122
|
+
e.preventDefault();
|
|
1123
|
+
e.stopPropagation();
|
|
1124
|
+
const hit = this.hitTestPointer(e);
|
|
1125
|
+
if (hit && hit.id === this.pointerDownGoId) {
|
|
1126
|
+
this.onSelect(hit, e);
|
|
1127
|
+
}
|
|
1128
|
+
this.pointerDownGoId = null;
|
|
1129
|
+
};
|
|
710
1130
|
}
|
|
711
1131
|
static { this.systemName = 'CombosDevelopmentToolSystem'; }
|
|
712
1132
|
init(params) {
|
|
713
1133
|
this.postMessageOrigin = params?.postMessageOrigin ?? '*';
|
|
714
1134
|
this.allowedMessageOrigins = mergeAllowedMessageOrigins(params?.allowedMessageOrigins);
|
|
1135
|
+
this.overlay = createOverlayCanvas();
|
|
1136
|
+
if (this.overlay) {
|
|
1137
|
+
this.overlay.canvas.addEventListener('pointerdown', this.onPointerDown);
|
|
1138
|
+
this.overlay.canvas.addEventListener('pointerup', this.onPointerUp);
|
|
1139
|
+
}
|
|
715
1140
|
if (typeof window !== 'undefined') {
|
|
716
1141
|
window.addEventListener(COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, this.onWindowSetPickMode);
|
|
717
|
-
window.addEventListener(COMBOS_DEVELOPMENT_TOOL_SET_MUTED, this.onWindowSetMuted);
|
|
718
1142
|
window.addEventListener(COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY, this.onWindowSetMarkerOverlay);
|
|
719
1143
|
window.addEventListener(COMBOS_DEVELOPMENT_TOOL_REFRESH, this.onWindowRefresh);
|
|
720
1144
|
window.addEventListener('message', this.onWindowMessage);
|
|
721
1145
|
}
|
|
722
1146
|
this.game.on(COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, this.onGameSetPickMode);
|
|
723
|
-
this.game.on(COMBOS_DEVELOPMENT_TOOL_SET_MUTED, this.onGameSetMuted);
|
|
724
1147
|
this.game.on(COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY, this.onGameSetMarkerOverlay);
|
|
725
1148
|
this.game.on(COMBOS_DEVELOPMENT_TOOL_REFRESH, this.onGameRefresh);
|
|
1149
|
+
this.syncOverlayMode();
|
|
726
1150
|
if (this.enabled) {
|
|
727
1151
|
this.needsRescan = true;
|
|
728
1152
|
}
|
|
729
1153
|
}
|
|
730
1154
|
onDestroy() {
|
|
1155
|
+
if (this.overlay) {
|
|
1156
|
+
this.overlay.canvas.removeEventListener('pointerdown', this.onPointerDown);
|
|
1157
|
+
this.overlay.canvas.removeEventListener('pointerup', this.onPointerUp);
|
|
1158
|
+
this.overlay.destroy();
|
|
1159
|
+
this.overlay = null;
|
|
1160
|
+
}
|
|
731
1161
|
if (typeof window !== 'undefined') {
|
|
732
1162
|
window.removeEventListener(COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, this.onWindowSetPickMode);
|
|
733
|
-
window.removeEventListener(COMBOS_DEVELOPMENT_TOOL_SET_MUTED, this.onWindowSetMuted);
|
|
734
1163
|
window.removeEventListener(COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY, this.onWindowSetMarkerOverlay);
|
|
735
1164
|
window.removeEventListener(COMBOS_DEVELOPMENT_TOOL_REFRESH, this.onWindowRefresh);
|
|
736
1165
|
window.removeEventListener('message', this.onWindowMessage);
|
|
737
1166
|
}
|
|
738
1167
|
this.game.off(COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, this.onGameSetPickMode);
|
|
739
|
-
this.game.off(COMBOS_DEVELOPMENT_TOOL_SET_MUTED, this.onGameSetMuted);
|
|
740
1168
|
this.game.off(COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY, this.onGameSetMarkerOverlay);
|
|
741
1169
|
this.game.off(COMBOS_DEVELOPMENT_TOOL_REFRESH, this.onGameRefresh);
|
|
742
|
-
this.
|
|
743
|
-
this.detachAllMarkers();
|
|
744
|
-
this.restoreDisabledGameEvents();
|
|
1170
|
+
this.markerOwners.clear();
|
|
745
1171
|
this.clearSelectionAndNotify('pick-disabled');
|
|
746
1172
|
}
|
|
747
1173
|
/** Programmatic toggle (same effect as events). */
|
|
@@ -749,14 +1175,14 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
749
1175
|
if (this.enabled === on)
|
|
750
1176
|
return;
|
|
751
1177
|
this.enabled = on;
|
|
1178
|
+
this.syncOverlayMode();
|
|
752
1179
|
if (!on) {
|
|
753
1180
|
this.clearSelectionAndNotify('pick-disabled');
|
|
754
|
-
this.detachAllPicks();
|
|
755
|
-
this.restoreDisabledGameEvents();
|
|
756
1181
|
this.postSetSuccess(false);
|
|
757
1182
|
}
|
|
758
1183
|
else {
|
|
759
1184
|
this.needsRescan = true;
|
|
1185
|
+
this.redrawOverlay();
|
|
760
1186
|
}
|
|
761
1187
|
}
|
|
762
1188
|
get isEnabled() {
|
|
@@ -765,27 +1191,21 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
765
1191
|
/**
|
|
766
1192
|
* Toggle the marker overlay: pin a persistent icon on every object that owns a
|
|
767
1193
|
* registered invisible component ({@link markerLayer}, Sound first). Independent
|
|
768
|
-
* of pick mode / mute.
|
|
769
|
-
*
|
|
770
|
-
* `CombosDevelopmentToolTarget` pick gets a clickable hit area. Selecting and
|
|
771
|
-
* persisting edits (e.g. volume) still go through that Target, exactly like any
|
|
772
|
-
* other scene edit, so the object must carry a Target (added by the game code).
|
|
1194
|
+
* of pick mode / mute. Selecting and persisting edits still go through the
|
|
1195
|
+
* owner's `CombosDevelopmentToolTarget`.
|
|
773
1196
|
*/
|
|
774
1197
|
setMarkerOverlay(on) {
|
|
775
1198
|
if (this.markerOverlayEnabled === on)
|
|
776
1199
|
return;
|
|
777
1200
|
this.markerOverlayEnabled = on;
|
|
1201
|
+
this.syncOverlayMode();
|
|
778
1202
|
if (on) {
|
|
779
|
-
// `attachMarkers` requests the pick rescan itself, once the icons exist.
|
|
780
1203
|
this.needsMarkerRescan = true;
|
|
781
1204
|
}
|
|
782
1205
|
else {
|
|
783
|
-
this.
|
|
1206
|
+
this.markerOwners.clear();
|
|
784
1207
|
this.postMarkerOverlaySuccess(false, []);
|
|
785
|
-
|
|
786
|
-
if (this.enabled) {
|
|
787
|
-
this.needsRescan = true;
|
|
788
|
-
}
|
|
1208
|
+
this.redrawOverlay();
|
|
789
1209
|
}
|
|
790
1210
|
}
|
|
791
1211
|
get isMarkerOverlayEnabled() {
|
|
@@ -800,64 +1220,42 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
800
1220
|
return this.mutedWithoutSoundSystem;
|
|
801
1221
|
}
|
|
802
1222
|
/**
|
|
803
|
-
*
|
|
804
|
-
*
|
|
1223
|
+
* @deprecated Host mute is handled by `@combos-fun/plugin-sound`.
|
|
1224
|
+
* Forwards to `SoundSystem.muted` when that system is registered.
|
|
805
1225
|
*/
|
|
806
1226
|
setMuted(muted) {
|
|
807
1227
|
const sound = this.getSoundSystem();
|
|
808
1228
|
if (sound) {
|
|
809
1229
|
sound.muted = muted;
|
|
1230
|
+
return;
|
|
810
1231
|
}
|
|
811
|
-
|
|
812
|
-
this.mutedWithoutSoundSystem = muted;
|
|
813
|
-
}
|
|
814
|
-
this.postStateChanged();
|
|
1232
|
+
this.mutedWithoutSoundSystem = muted;
|
|
815
1233
|
}
|
|
816
|
-
update(
|
|
1234
|
+
update() {
|
|
817
1235
|
if (this.enabled && this.needsRescan) {
|
|
818
1236
|
this.needsRescan = false;
|
|
819
|
-
this.attachEditMode();
|
|
820
1237
|
this.postSetSuccess(true);
|
|
821
1238
|
}
|
|
822
|
-
// Attaching markers is deferred to run *after* the pick rescan above so that a
|
|
823
|
-
// newly drawn icon's rebuild lands on a later frame (see `attachMarkers`): the
|
|
824
|
-
// icon child needs a rendered container before it contributes to the owner's
|
|
825
|
-
// bounds, and only then does the owner's Target pick get a hit area over it.
|
|
826
1239
|
if (this.markerOverlayEnabled && this.needsMarkerRescan) {
|
|
827
1240
|
this.needsMarkerRescan = false;
|
|
828
1241
|
this.attachMarkers();
|
|
829
1242
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
this.releasePick(go);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
// Drop markers whose owner disappeared while the overlay is on.
|
|
836
|
-
for (const [ownerId, marker] of [...this.markerGoByOwner.entries()]) {
|
|
837
|
-
const owner = this.findGameObjectById(ownerId);
|
|
838
|
-
if (!owner || owner.destroyed || marker.destroyed) {
|
|
839
|
-
this.removeMarker(ownerId);
|
|
840
|
-
}
|
|
1243
|
+
if (this.selected?.destroyed) {
|
|
1244
|
+
this.clearSelectionAndNotify('target-removed');
|
|
841
1245
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
this.
|
|
1246
|
+
}
|
|
1247
|
+
lateUpdate(_e) {
|
|
1248
|
+
this.redrawOverlay();
|
|
845
1249
|
}
|
|
846
1250
|
componentChanged(changed) {
|
|
847
|
-
if (!this.enabled)
|
|
848
|
-
return;
|
|
849
1251
|
const { type, gameObject, componentName } = changed;
|
|
850
1252
|
if (!gameObject || componentName !== 'CombosDevelopmentToolTarget')
|
|
851
1253
|
return;
|
|
852
|
-
if (type === OBSERVER_TYPE.
|
|
853
|
-
this.
|
|
854
|
-
this.ensurePick(gameObject);
|
|
1254
|
+
if (type === OBSERVER_TYPE.REMOVE && this.selected === gameObject) {
|
|
1255
|
+
this.clearSelectionAndNotify('target-removed');
|
|
855
1256
|
}
|
|
856
|
-
|
|
857
|
-
this.
|
|
858
|
-
if (this.selected === gameObject) {
|
|
859
|
-
this.clearSelectionAndNotify('target-removed');
|
|
860
|
-
}
|
|
1257
|
+
if (this.markerOverlayEnabled) {
|
|
1258
|
+
this.needsMarkerRescan = true;
|
|
861
1259
|
}
|
|
862
1260
|
}
|
|
863
1261
|
/** Re-bind pick targets / markers after dynamic scene changes while active. */
|
|
@@ -869,24 +1267,10 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
869
1267
|
this.needsMarkerRescan = true;
|
|
870
1268
|
}
|
|
871
1269
|
}
|
|
872
|
-
|
|
873
|
-
this.
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
this.collectGameObjects(tr.gameObject, list);
|
|
877
|
-
}
|
|
878
|
-
for (const go of list) {
|
|
879
|
-
if (this.isIgnoredPickGo(go))
|
|
880
|
-
continue;
|
|
881
|
-
this.stripGameEvent(go);
|
|
882
|
-
}
|
|
883
|
-
for (const go of list) {
|
|
884
|
-
if (this.isIgnoredPickGo(go))
|
|
885
|
-
continue;
|
|
886
|
-
if (!go.getComponent(CombosDevelopmentToolTarget))
|
|
887
|
-
continue;
|
|
888
|
-
this.ensurePick(go);
|
|
889
|
-
}
|
|
1270
|
+
syncOverlayMode() {
|
|
1271
|
+
const active = this.enabled || this.markerOverlayEnabled;
|
|
1272
|
+
this.overlay?.setVisible(active);
|
|
1273
|
+
this.overlay?.setPickEnabled(this.enabled);
|
|
890
1274
|
}
|
|
891
1275
|
collectGameObjects(go, out) {
|
|
892
1276
|
out.push(go);
|
|
@@ -894,203 +1278,134 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
894
1278
|
this.collectGameObjects(tr.gameObject, out);
|
|
895
1279
|
}
|
|
896
1280
|
}
|
|
1281
|
+
listSceneObjects() {
|
|
1282
|
+
const list = [];
|
|
1283
|
+
const scene = this.game.scene;
|
|
1284
|
+
if (!scene?.transform?.children)
|
|
1285
|
+
return list;
|
|
1286
|
+
for (const tr of scene.transform.children) {
|
|
1287
|
+
this.collectGameObjects(tr.gameObject, list);
|
|
1288
|
+
}
|
|
1289
|
+
return list;
|
|
1290
|
+
}
|
|
897
1291
|
isIgnoredPickGo(go) {
|
|
898
|
-
return go.name === OUTLINE_GO_NAME ||
|
|
1292
|
+
return go.name === OUTLINE_GO_NAME || this.isMarkerGo(go);
|
|
899
1293
|
}
|
|
900
1294
|
isMarkerGo(go) {
|
|
901
1295
|
return typeof go.name === 'string' && go.name.startsWith(MARKER_GO_NAME_PREFIX);
|
|
902
1296
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (!container)
|
|
911
|
-
return;
|
|
912
|
-
try {
|
|
913
|
-
container.interactive = false;
|
|
914
|
-
this.disabledGameEventGoIds.add(go.id);
|
|
915
|
-
}
|
|
916
|
-
catch {
|
|
917
|
-
/* ignore */
|
|
918
|
-
}
|
|
1297
|
+
projectGo(go) {
|
|
1298
|
+
return projectGameObjectToScreen(go, {
|
|
1299
|
+
css: this.overlayCss,
|
|
1300
|
+
design: resolveDesignSize(this.game),
|
|
1301
|
+
camera: resolveCamera(this.game),
|
|
1302
|
+
object3D: resolveObject3D(this.game, go.id),
|
|
1303
|
+
});
|
|
919
1304
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
if (
|
|
924
|
-
this.disabledGameEventGoIds.delete(id);
|
|
1305
|
+
collectOverlayHits() {
|
|
1306
|
+
const hits = [];
|
|
1307
|
+
for (const go of this.listSceneObjects()) {
|
|
1308
|
+
if (go.destroyed || this.isIgnoredPickGo(go))
|
|
925
1309
|
continue;
|
|
1310
|
+
if (!go.getComponent(CombosDevelopmentToolTarget))
|
|
1311
|
+
continue;
|
|
1312
|
+
const rect = this.projectGo(go);
|
|
1313
|
+
if (rect) {
|
|
1314
|
+
hits.push({ go, rect });
|
|
926
1315
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
932
|
-
catch {
|
|
933
|
-
/* ignore */
|
|
1316
|
+
if (this.markerOverlayEnabled && resolveMarkerDef(go)) {
|
|
1317
|
+
const markerRect = this.markerRectFor(go, rect);
|
|
1318
|
+
if (markerRect) {
|
|
1319
|
+
hits.push({ go, rect: markerRect });
|
|
934
1320
|
}
|
|
935
1321
|
}
|
|
936
|
-
this.disabledGameEventGoIds.delete(id);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
detachAllPicks() {
|
|
940
|
-
for (const go of [...this.tapOwners.values()]) {
|
|
941
|
-
this.releasePick(go);
|
|
942
1322
|
}
|
|
1323
|
+
return hits;
|
|
943
1324
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const container = this.getRendererContainer(go);
|
|
948
|
-
if (container) {
|
|
949
|
-
try {
|
|
950
|
-
container.interactive = true;
|
|
951
|
-
}
|
|
952
|
-
catch {
|
|
953
|
-
/* ignore */
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
let ev = go.getComponent(Event);
|
|
957
|
-
if (!ev) {
|
|
958
|
-
ev = go.addComponent(this.createPickEvent(go));
|
|
959
|
-
this.pickEvents.add(go.id);
|
|
960
|
-
}
|
|
961
|
-
const handler = payload => this.onSelect(go, payload);
|
|
962
|
-
this.tapHandlers.set(go.id, handler);
|
|
963
|
-
this.tapOwners.set(go.id, go);
|
|
964
|
-
ev.on('tap', handler);
|
|
965
|
-
}
|
|
966
|
-
createPickEvent(go) {
|
|
967
|
-
const bounds = this.resolvePickBounds(go);
|
|
968
|
-
if (bounds.width > 0 && bounds.height > 0) {
|
|
969
|
-
return new Event({
|
|
970
|
-
hitArea: {
|
|
971
|
-
type: HIT_AREA_TYPE.Rect,
|
|
972
|
-
style: {
|
|
973
|
-
x: bounds.x,
|
|
974
|
-
y: bounds.y,
|
|
975
|
-
width: bounds.width,
|
|
976
|
-
height: bounds.height,
|
|
977
|
-
},
|
|
978
|
-
},
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
return new Event();
|
|
982
|
-
}
|
|
983
|
-
resolvePickBounds(go) {
|
|
984
|
-
if (this.shouldPreferRenderedBounds(go)) {
|
|
985
|
-
const fromOwnGraphics = this.resolveOwnGraphicsLocalBounds(go);
|
|
986
|
-
if (fromOwnGraphics) {
|
|
987
|
-
return fromOwnGraphics;
|
|
988
|
-
}
|
|
989
|
-
const fromGraphics = this.resolveContainerLocalBounds(go);
|
|
990
|
-
if (fromGraphics) {
|
|
991
|
-
return fromGraphics;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
const { width, height } = go.transform.size;
|
|
995
|
-
if (width > 0 && height > 0) {
|
|
996
|
-
return { x: 0, y: 0, width, height };
|
|
997
|
-
}
|
|
998
|
-
const fromContainer = this.resolveContainerLocalBounds(go);
|
|
999
|
-
if (fromContainer) {
|
|
1000
|
-
return fromContainer;
|
|
1001
|
-
}
|
|
1002
|
-
return { x: 0, y: 0, width: 1, height: 1 };
|
|
1003
|
-
}
|
|
1004
|
-
shouldPreferRenderedBounds(go) {
|
|
1005
|
-
return go.getComponent(Graphics) != null;
|
|
1006
|
-
}
|
|
1007
|
-
/** Bounds of this GO's own Graphics only — excludes child GOs such as the selection outline. */
|
|
1008
|
-
resolveOwnGraphicsLocalBounds(go) {
|
|
1009
|
-
const gfx = go.getComponent(Graphics);
|
|
1010
|
-
if (!gfx?.graphics)
|
|
1325
|
+
markerRectFor(go, objectRect) {
|
|
1326
|
+
const rect = objectRect ?? this.projectGo(go);
|
|
1327
|
+
if (!rect)
|
|
1011
1328
|
return null;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
height: bounds.height,
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1329
|
+
if (go.getComponent('Transform3D')) {
|
|
1330
|
+
return {
|
|
1331
|
+
x: rect.x + rect.width / 2 - MARKER_ICON_SIZE / 2,
|
|
1332
|
+
y: rect.y + rect.height / 2 - MARKER_ICON_SIZE / 2,
|
|
1333
|
+
width: MARKER_ICON_SIZE,
|
|
1334
|
+
height: MARKER_ICON_SIZE,
|
|
1335
|
+
};
|
|
1022
1336
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
const container = this.getRendererContainer(go);
|
|
1030
|
-
if (!container)
|
|
1031
|
-
return null;
|
|
1032
|
-
const outlineContainer = this.getOutlineContainerIfChildOf(go);
|
|
1033
|
-
let outlineDetached = false;
|
|
1034
|
-
if (outlineContainer?.parent === container) {
|
|
1035
|
-
container.removeChild(outlineContainer);
|
|
1036
|
-
outlineDetached = true;
|
|
1037
|
-
}
|
|
1038
|
-
try {
|
|
1039
|
-
const bounds = container.getLocalBounds();
|
|
1040
|
-
if (bounds.width > 0 && bounds.height > 0) {
|
|
1041
|
-
return {
|
|
1042
|
-
x: bounds.x,
|
|
1043
|
-
y: bounds.y,
|
|
1044
|
-
width: bounds.width,
|
|
1045
|
-
height: bounds.height,
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
catch {
|
|
1050
|
-
/* ignore */
|
|
1051
|
-
}
|
|
1052
|
-
finally {
|
|
1053
|
-
if (outlineDetached && outlineContainer) {
|
|
1054
|
-
container.addChild(outlineContainer);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
return null;
|
|
1337
|
+
return {
|
|
1338
|
+
x: rect.x,
|
|
1339
|
+
y: rect.y,
|
|
1340
|
+
width: MARKER_ICON_SIZE,
|
|
1341
|
+
height: MARKER_ICON_SIZE,
|
|
1342
|
+
};
|
|
1058
1343
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
if (!
|
|
1062
|
-
return
|
|
1344
|
+
overlayPoint(e) {
|
|
1345
|
+
const canvas = this.overlay?.canvas;
|
|
1346
|
+
if (!canvas)
|
|
1347
|
+
return { x: 0, y: 0 };
|
|
1348
|
+
const rect = canvas.getBoundingClientRect();
|
|
1349
|
+
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
1350
|
+
}
|
|
1351
|
+
hitTestPointer(e) {
|
|
1352
|
+
const pt = this.overlayPoint(e);
|
|
1353
|
+
const hits = this.collectOverlayHits();
|
|
1354
|
+
return hitTestRects(pt.x, pt.y, hits.map(h => ({ id: h.go, rect: h.rect })));
|
|
1355
|
+
}
|
|
1356
|
+
redrawOverlay() {
|
|
1357
|
+
const overlay = this.overlay;
|
|
1358
|
+
if (!overlay)
|
|
1359
|
+
return;
|
|
1360
|
+
const active = this.enabled || this.markerOverlayEnabled;
|
|
1361
|
+
if (!active) {
|
|
1362
|
+
overlay.setVisible(false);
|
|
1363
|
+
return;
|
|
1063
1364
|
}
|
|
1064
|
-
|
|
1365
|
+
const gameCanvas = this.game.canvas ?? this.game.scene?.canvas;
|
|
1366
|
+
if (!gameCanvas)
|
|
1367
|
+
return;
|
|
1368
|
+
const css = overlay.sync(gameCanvas);
|
|
1369
|
+
if (!css)
|
|
1370
|
+
return;
|
|
1371
|
+
this.overlayCss = css;
|
|
1372
|
+
overlay.ctx.clearRect(0, 0, css.width, css.height);
|
|
1373
|
+
this.drawMarkers(overlay.ctx);
|
|
1374
|
+
this.drawSelectionOutline(overlay.ctx);
|
|
1065
1375
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1376
|
+
drawMarkers(ctx) {
|
|
1377
|
+
if (!this.markerOverlayEnabled)
|
|
1378
|
+
return;
|
|
1379
|
+
const gfx = new CanvasMarkerGraphics(ctx);
|
|
1380
|
+
for (const go of this.listSceneObjects()) {
|
|
1381
|
+
if (go.destroyed || this.isIgnoredPickGo(go))
|
|
1382
|
+
continue;
|
|
1383
|
+
const def = resolveMarkerDef(go);
|
|
1384
|
+
if (!def)
|
|
1385
|
+
continue;
|
|
1386
|
+
const objectRect = this.projectGo(go);
|
|
1387
|
+
const rect = this.markerRectFor(go, objectRect);
|
|
1388
|
+
if (!rect)
|
|
1389
|
+
continue;
|
|
1390
|
+
ctx.save();
|
|
1391
|
+
ctx.translate(rect.x, rect.y);
|
|
1392
|
+
drawMarkerIcon(gfx, def, MARKER_ICON_SIZE);
|
|
1393
|
+
ctx.restore();
|
|
1075
1394
|
}
|
|
1076
1395
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
if (!handler)
|
|
1396
|
+
drawSelectionOutline(ctx) {
|
|
1397
|
+
if (!this.enabled || !this.selected || this.selected.destroyed)
|
|
1080
1398
|
return;
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
this.
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
this.pickEvents.delete(go.id);
|
|
1093
|
-
}
|
|
1399
|
+
const rect = this.projectGo(this.selected);
|
|
1400
|
+
if (!rect)
|
|
1401
|
+
return;
|
|
1402
|
+
const design = resolveDesignSize(this.game);
|
|
1403
|
+
const scale = design ? this.overlayCss.width / design.width : 1;
|
|
1404
|
+
ctx.save();
|
|
1405
|
+
ctx.strokeStyle = OUTLINE_COLOR;
|
|
1406
|
+
ctx.lineWidth = Math.max(2, 4 * (Number.isFinite(scale) && scale > 0 ? scale : 1));
|
|
1407
|
+
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
1408
|
+
ctx.restore();
|
|
1094
1409
|
}
|
|
1095
1410
|
postSetSuccess(enabled) {
|
|
1096
1411
|
const payload = {
|
|
@@ -1102,34 +1417,16 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
1102
1417
|
}
|
|
1103
1418
|
this.game.emit(COMBOS_DEVELOPMENT_TOOL_PICK_MODE_SUCCESS, { enabled });
|
|
1104
1419
|
}
|
|
1105
|
-
postStateChanged() {
|
|
1106
|
-
const state = {
|
|
1107
|
-
muted: this.isMuted,
|
|
1108
|
-
};
|
|
1109
|
-
const payload = {
|
|
1110
|
-
type: COMBOS_DEVELOPMENT_TOOL_STATE_CHANGED,
|
|
1111
|
-
...state,
|
|
1112
|
-
};
|
|
1113
|
-
if (typeof window !== 'undefined' && window.parent && window.parent !== window) {
|
|
1114
|
-
window.parent.postMessage(payload, this.postMessageOrigin);
|
|
1115
|
-
}
|
|
1116
|
-
this.game.emit(COMBOS_DEVELOPMENT_TOOL_STATE_CHANGED, state);
|
|
1117
|
-
}
|
|
1118
|
-
/** (Re)build markers for every object owning a registered marker component. */
|
|
1119
1420
|
attachMarkers() {
|
|
1120
|
-
this.
|
|
1121
|
-
const list = [];
|
|
1122
|
-
for (const tr of this.game.scene.transform.children) {
|
|
1123
|
-
this.collectGameObjects(tr.gameObject, list);
|
|
1124
|
-
}
|
|
1421
|
+
this.markerOwners.clear();
|
|
1125
1422
|
const counts = new Map();
|
|
1126
|
-
for (const go of
|
|
1423
|
+
for (const go of this.listSceneObjects()) {
|
|
1127
1424
|
if (this.isIgnoredPickGo(go))
|
|
1128
1425
|
continue;
|
|
1129
1426
|
const def = resolveMarkerDef(go);
|
|
1130
1427
|
if (!def)
|
|
1131
1428
|
continue;
|
|
1132
|
-
this.
|
|
1429
|
+
this.markerOwners.add(go.id);
|
|
1133
1430
|
counts.set(def.componentName, (counts.get(def.componentName) ?? 0) + 1);
|
|
1134
1431
|
}
|
|
1135
1432
|
const markers = [...counts.entries()].map(([componentName, count]) => ({
|
|
@@ -1137,43 +1434,7 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
1137
1434
|
count,
|
|
1138
1435
|
}));
|
|
1139
1436
|
this.postMarkerOverlaySuccess(true, markers);
|
|
1140
|
-
|
|
1141
|
-
// before they enlarge their owners' bounds into a clickable hit area. Runs on
|
|
1142
|
-
// a later frame because the pick rescan block already executed above.
|
|
1143
|
-
if (this.enabled) {
|
|
1144
|
-
this.needsRescan = true;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
createMarker(owner, def) {
|
|
1148
|
-
if (this.markerGoByOwner.has(owner.id))
|
|
1149
|
-
return;
|
|
1150
|
-
const bounds = this.resolvePickBounds(owner);
|
|
1151
|
-
const marker = new GameObject(MARKER_GO_NAME_PREFIX + owner.id, {
|
|
1152
|
-
size: { width: MARKER_ICON_SIZE, height: MARKER_ICON_SIZE },
|
|
1153
|
-
position: { x: bounds.x, y: bounds.y },
|
|
1154
|
-
origin: { x: 0, y: 0 },
|
|
1155
|
-
});
|
|
1156
|
-
const gfx = marker.addComponent(new Graphics());
|
|
1157
|
-
if (gfx?.graphics) {
|
|
1158
|
-
drawMarkerIcon(gfx.graphics, def, MARKER_ICON_SIZE);
|
|
1159
|
-
}
|
|
1160
|
-
owner.addChild(marker);
|
|
1161
|
-
this.markerGoByOwner.set(owner.id, marker);
|
|
1162
|
-
}
|
|
1163
|
-
removeMarker(ownerId) {
|
|
1164
|
-
const marker = this.markerGoByOwner.get(ownerId);
|
|
1165
|
-
if (!marker)
|
|
1166
|
-
return;
|
|
1167
|
-
this.markerGoByOwner.delete(ownerId);
|
|
1168
|
-
if (!marker.destroyed) {
|
|
1169
|
-
marker.destroy();
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
detachAllMarkers() {
|
|
1173
|
-
for (const ownerId of [...this.markerGoByOwner.keys()]) {
|
|
1174
|
-
this.removeMarker(ownerId);
|
|
1175
|
-
}
|
|
1176
|
-
this.markerGoByOwner.clear();
|
|
1437
|
+
this.redrawOverlay();
|
|
1177
1438
|
}
|
|
1178
1439
|
postMarkerOverlaySuccess(enabled, markers) {
|
|
1179
1440
|
const total = markers.reduce((sum, m) => sum + m.count, 0);
|
|
@@ -1197,59 +1458,33 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
1197
1458
|
}
|
|
1198
1459
|
return system;
|
|
1199
1460
|
}
|
|
1200
|
-
|
|
1201
|
-
if (this.outlineGo)
|
|
1202
|
-
return;
|
|
1203
|
-
const go = new GameObject(OUTLINE_GO_NAME, {
|
|
1204
|
-
size: { width: 1, height: 1 },
|
|
1205
|
-
position: { x: 0, y: 0 },
|
|
1206
|
-
origin: { x: 0, y: 0 },
|
|
1207
|
-
});
|
|
1208
|
-
go.addComponent(new Graphics());
|
|
1209
|
-
this.outlineGo = go;
|
|
1210
|
-
}
|
|
1211
|
-
onSelect(go, tap) {
|
|
1461
|
+
onSelect(go, e) {
|
|
1212
1462
|
if (!this.enabled)
|
|
1213
1463
|
return;
|
|
1214
|
-
tap?.stopPropagation?.();
|
|
1215
|
-
this.ensureOutline();
|
|
1216
|
-
if (!this.outlineGo)
|
|
1217
|
-
return;
|
|
1218
1464
|
this.selected = go;
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
go.addChild(this.outlineGo);
|
|
1223
|
-
this.invalidateOutlineBounds();
|
|
1224
|
-
this.updateSelectionOutline();
|
|
1465
|
+
this.redrawOverlay();
|
|
1466
|
+
const overlayPt = this.overlayPoint(e);
|
|
1467
|
+
const pointer = cssToDesign(overlayPt, resolveDesignSize(this.game), this.overlayCss);
|
|
1225
1468
|
const snapshot = buildGameObjectSnapshot(go);
|
|
1226
1469
|
const payload = {
|
|
1227
1470
|
type: COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED,
|
|
1228
1471
|
snapshot,
|
|
1229
|
-
pointer
|
|
1472
|
+
pointer,
|
|
1230
1473
|
};
|
|
1231
1474
|
if (typeof window !== 'undefined' && window.parent && window.parent !== window) {
|
|
1232
1475
|
window.parent.postMessage(payload, this.postMessageOrigin);
|
|
1233
1476
|
}
|
|
1234
1477
|
}
|
|
1235
1478
|
findGameObjectById(id) {
|
|
1236
|
-
|
|
1237
|
-
for (const tr of this.game.scene.transform.children) {
|
|
1238
|
-
this.collectGameObjects(tr.gameObject, stack);
|
|
1239
|
-
}
|
|
1240
|
-
return stack.find(go => go.id === id) ?? null;
|
|
1479
|
+
return this.listSceneObjects().find(go => go.id === id) ?? null;
|
|
1241
1480
|
}
|
|
1242
1481
|
findGameObjectBySource(source) {
|
|
1243
|
-
const stack = [];
|
|
1244
|
-
for (const tr of this.game.scene.transform.children) {
|
|
1245
|
-
this.collectGameObjects(tr.gameObject, stack);
|
|
1246
|
-
}
|
|
1247
1482
|
const wantFile = source.file.trim();
|
|
1248
1483
|
const wantAnchor = (source.anchor ?? '').trim();
|
|
1249
1484
|
if (!wantAnchor) {
|
|
1250
1485
|
return null;
|
|
1251
1486
|
}
|
|
1252
|
-
for (const go of
|
|
1487
|
+
for (const go of this.listSceneObjects()) {
|
|
1253
1488
|
const anchor = readSourceAnchor(go);
|
|
1254
1489
|
if (!anchor || anchor.file !== wantFile) {
|
|
1255
1490
|
continue;
|
|
@@ -1293,8 +1528,7 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
1293
1528
|
if (!applied)
|
|
1294
1529
|
return;
|
|
1295
1530
|
if (this.selected?.id === go.id) {
|
|
1296
|
-
this.
|
|
1297
|
-
this.updateSelectionOutline();
|
|
1531
|
+
this.redrawOverlay();
|
|
1298
1532
|
this.postSnapshotUpdate(go);
|
|
1299
1533
|
}
|
|
1300
1534
|
}
|
|
@@ -1311,7 +1545,8 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
1311
1545
|
}
|
|
1312
1546
|
clearSelectionAndNotify(reason) {
|
|
1313
1547
|
const hadSelection = this.selected != null;
|
|
1314
|
-
this.
|
|
1548
|
+
this.selected = null;
|
|
1549
|
+
this.redrawOverlay();
|
|
1315
1550
|
if (!shouldNotifyGameObjectDeselected(hadSelection)) {
|
|
1316
1551
|
return;
|
|
1317
1552
|
}
|
|
@@ -1324,44 +1559,6 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends Syst
|
|
|
1324
1559
|
}
|
|
1325
1560
|
this.game.emit(payload.type, { reason });
|
|
1326
1561
|
}
|
|
1327
|
-
clearSelection() {
|
|
1328
|
-
this.selected = null;
|
|
1329
|
-
this.invalidateOutlineBounds();
|
|
1330
|
-
if (this.outlineGo?.parent) {
|
|
1331
|
-
this.outlineGo.remove();
|
|
1332
|
-
}
|
|
1333
|
-
const gfx = this.outlineGo?.getComponent(Graphics);
|
|
1334
|
-
if (gfx?.graphics) {
|
|
1335
|
-
gfx.graphics.clear();
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
/** Redraw the green outline only when content bounds change (movement follows via child transform). */
|
|
1339
|
-
updateSelectionOutline() {
|
|
1340
|
-
if (!this.enabled || !this.selected || !this.outlineGo)
|
|
1341
|
-
return;
|
|
1342
|
-
const gfxComp = this.outlineGo.getComponent(Graphics);
|
|
1343
|
-
if (!gfxComp?.graphics)
|
|
1344
|
-
return;
|
|
1345
|
-
const bounds = this.resolvePickBounds(this.selected);
|
|
1346
|
-
if (this.lastOutlineBounds && this.pickBoundsEqual(this.lastOutlineBounds, bounds)) {
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
this.lastOutlineBounds = bounds;
|
|
1350
|
-
const g = gfxComp.graphics;
|
|
1351
|
-
g.clear();
|
|
1352
|
-
g.rect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1353
|
-
g.stroke({ width: 4, color: 0x55ffaa, alpha: 1 });
|
|
1354
|
-
}
|
|
1355
|
-
invalidateOutlineBounds() {
|
|
1356
|
-
this.lastOutlineBounds = null;
|
|
1357
|
-
}
|
|
1358
|
-
pickBoundsEqual(a, b) {
|
|
1359
|
-
const eps = 0.01;
|
|
1360
|
-
return (Math.abs(a.x - b.x) < eps &&
|
|
1361
|
-
Math.abs(a.y - b.y) < eps &&
|
|
1362
|
-
Math.abs(a.width - b.width) < eps &&
|
|
1363
|
-
Math.abs(a.height - b.height) < eps);
|
|
1364
|
-
}
|
|
1365
1562
|
};
|
|
1366
1563
|
CombosDevelopmentToolSystem = __decorate([
|
|
1367
1564
|
decorators.componentObserver({
|
|
@@ -1373,8 +1570,51 @@ var CombosDevelopmentToolSystem$1 = CombosDevelopmentToolSystem;
|
|
|
1373
1570
|
/** Auto-generated by scripts/build-package.mjs — do not edit. */
|
|
1374
1571
|
Object.assign(CombosDevelopmentToolSystem$1, {
|
|
1375
1572
|
packageName: "@combos-fun/plugin-development-tool",
|
|
1376
|
-
packageVersion: "0.0.
|
|
1573
|
+
packageVersion: "0.0.49",
|
|
1377
1574
|
});
|
|
1378
1575
|
|
|
1379
|
-
|
|
1576
|
+
function isInternalEditorGo(go) {
|
|
1577
|
+
return typeof go.name === 'string' && go.name.startsWith('__');
|
|
1578
|
+
}
|
|
1579
|
+
function isSceneLike(go) {
|
|
1580
|
+
return Array.isArray(go.gameObjects);
|
|
1581
|
+
}
|
|
1582
|
+
function sourceIsComplete(raw) {
|
|
1583
|
+
if (!isSceneSourceAnchor(raw))
|
|
1584
|
+
return false;
|
|
1585
|
+
return Boolean(raw.file.trim()) && Boolean((raw.anchor ?? '').trim());
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Marks `go` for editor pick / persist. Idempotent: existing Targets keep a
|
|
1589
|
+
* complete `payload.source`; missing identity is filled from `source`.
|
|
1590
|
+
*
|
|
1591
|
+
* Inserted by `@combos-fun/plugin-development-tool/vite`. Game code should
|
|
1592
|
+
* not call this by hand.
|
|
1593
|
+
*/
|
|
1594
|
+
function attachDevelopmentTarget(go, source) {
|
|
1595
|
+
if (!go || go.destroyed || isInternalEditorGo(go) || isSceneLike(go)) {
|
|
1596
|
+
return go;
|
|
1597
|
+
}
|
|
1598
|
+
const file = source.file.trim();
|
|
1599
|
+
const anchor = source.anchor.trim();
|
|
1600
|
+
if (!file || !anchor) {
|
|
1601
|
+
return go;
|
|
1602
|
+
}
|
|
1603
|
+
const existing = go.getComponent(CombosDevelopmentToolTarget);
|
|
1604
|
+
if (existing) {
|
|
1605
|
+
if (!sourceIsComplete(existing.payload?.source)) {
|
|
1606
|
+
existing.payload = {
|
|
1607
|
+
...(existing.payload ?? {}),
|
|
1608
|
+
source: { file, anchor },
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
return go;
|
|
1612
|
+
}
|
|
1613
|
+
go.addComponent(new CombosDevelopmentToolTarget({
|
|
1614
|
+
payload: { source: { file, anchor } },
|
|
1615
|
+
}));
|
|
1616
|
+
return go;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
export { COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY, COMBOS_DEVELOPMENT_TOOL_CLEAR_SELECTION, COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_DESELECTED, COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED, COMBOS_DEVELOPMENT_TOOL_MARKER_OVERLAY_SUCCESS, COMBOS_DEVELOPMENT_TOOL_PICK_MODE_SUCCESS, COMBOS_DEVELOPMENT_TOOL_READY, COMBOS_DEVELOPMENT_TOOL_REFRESH, COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY, COMBOS_DEVELOPMENT_TOOL_SET_MUTED, COMBOS_DEVELOPMENT_TOOL_SET_PICK_MODE, COMBOS_DEVELOPMENT_TOOL_STATE_CHANGED, CombosDevelopmentToolSystem$1 as CombosDevelopmentToolSystem, CombosDevelopmentToolTarget, DEFAULT_ALLOWED_MESSAGE_HOST_SUFFIXES, MARKER_COMPONENTS, MARKER_ICON_SIZE, applyPropertyValue, applyPropertyWithHooks, attachDevelopmentTarget, buildGameObjectDeselectedPayload, buildGameObjectSnapshot, drawMarkerIcon, isAllowedMessageOrigin, isClearSelectionMessage, mergeAllowedMessageOrigins, readPropertyValue, readSourceAnchor, resolveMarkerDef, shouldNotifyGameObjectDeselected };
|
|
1380
1620
|
//# sourceMappingURL=plugin-development-tool.esm.js.map
|