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