@combos-fun/plugin-development-tool 0.0.13 → 0.0.14

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.
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var engine = require('@combos-fun/engine');
4
3
  var tslib = require('tslib');
4
+ var engine = require('@combos-fun/engine');
5
5
  var pluginRendererGraphics = require('@combos-fun/plugin-renderer-graphics');
6
6
  var pluginRendererEvent = require('@combos-fun/plugin-renderer-event');
7
7
 
@@ -11,6 +11,10 @@ const COMBOS_DEVELOPMENT_TOOL_SET = 'combos-development-tool:set';
11
11
  const COMBOS_DEVELOPMENT_TOOL_REFRESH = 'combos-development-tool:refresh';
12
12
  /** Emitted to `window.parent` when an object is selected while the tool is on. */
13
13
  const COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED = 'combos-development-tool:gameobject-selected';
14
+ /** Parent → iframe: apply a value to a component field (live preview). */
15
+ const COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY = 'combos-development-tool:apply-property';
16
+ /** iframe → parent: game scene bootstrap finished; parent may enable pick mode. */
17
+ const COMBOS_DEVELOPMENT_TOOL_READY = 'combos-development-tool:ready';
14
18
 
15
19
  /**
16
20
  * Optional marker when using `CombosDevelopmentToolSystem` with `scope: 'tagged'`, or to attach
@@ -27,6 +31,243 @@ class CombosDevelopmentToolTarget extends engine.Component {
27
31
  }
28
32
  }
29
33
 
34
+ function isSceneSourceAnchor(v) {
35
+ return (!!v &&
36
+ typeof v === 'object' &&
37
+ typeof v.file === 'string');
38
+ }
39
+
40
+ const SKIP_KEYS = new Set([
41
+ 'gameObject',
42
+ 'name',
43
+ 'started',
44
+ '__componentDefaultParams',
45
+ 'destroyed',
46
+ 'inScene',
47
+ 'worldTransform',
48
+ 'children',
49
+ '_parent',
50
+ ]);
51
+ const COMPONENT_SKIP_KEYS = {
52
+ Physics: new Set([
53
+ 'body',
54
+ 'Body',
55
+ 'PhysicsEngine',
56
+ 'World',
57
+ 'Constraint',
58
+ 'mouseConstraint',
59
+ 'bodyParams',
60
+ ]),
61
+ Event: new Set(['hitArea']),
62
+ };
63
+ function cloneJsonSafe(value, depth = 0) {
64
+ if (value === null || value === undefined)
65
+ return value;
66
+ if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') {
67
+ return value;
68
+ }
69
+ if (depth > 6)
70
+ return undefined;
71
+ if (Array.isArray(value)) {
72
+ return value.map(item => cloneJsonSafe(item, depth + 1));
73
+ }
74
+ if (typeof value === 'object') {
75
+ const out = {};
76
+ for (const [k, v] of Object.entries(value)) {
77
+ if (typeof v === 'function')
78
+ continue;
79
+ const cloned = cloneJsonSafe(v, depth + 1);
80
+ if (cloned !== undefined)
81
+ out[k] = cloned;
82
+ }
83
+ return out;
84
+ }
85
+ return undefined;
86
+ }
87
+ function serializeComponent(comp) {
88
+ const out = {};
89
+ const componentSkips = COMPONENT_SKIP_KEYS[comp.name];
90
+ for (const key of Object.keys(comp)) {
91
+ if (SKIP_KEYS.has(key) || key.startsWith('_'))
92
+ continue;
93
+ if (componentSkips?.has(key))
94
+ continue;
95
+ const value = comp[key];
96
+ if (typeof value === 'function')
97
+ continue;
98
+ const cloned = cloneJsonSafe(value);
99
+ if (cloned !== undefined)
100
+ out[key] = cloned;
101
+ }
102
+ return out;
103
+ }
104
+ function readSourceAnchor(go) {
105
+ const tag = go.getComponent(CombosDevelopmentToolTarget);
106
+ const raw = tag?.payload?.source;
107
+ if (!isSceneSourceAnchor(raw))
108
+ return undefined;
109
+ return {
110
+ file: raw.file,
111
+ anchor: raw.anchor ?? go.name,
112
+ };
113
+ }
114
+ function buildGameObjectSnapshot(go) {
115
+ const components = go.components
116
+ .filter(c => c.name !== 'CombosDevelopmentToolTarget')
117
+ .map(c => ({
118
+ componentName: c.name,
119
+ fields: serializeComponent(c),
120
+ }));
121
+ return {
122
+ id: go.id,
123
+ name: go.name,
124
+ components,
125
+ source: readSourceAnchor(go),
126
+ };
127
+ }
128
+ function applyPropertyValue(go, componentName, path, value) {
129
+ const comp = go.getComponent(componentName);
130
+ if (!comp || !path.length)
131
+ return false;
132
+ let target = comp;
133
+ for (let i = 0; i < path.length - 1; i++) {
134
+ const key = path[i];
135
+ if (target[key] === undefined || target[key] === null) {
136
+ target[key] = {};
137
+ }
138
+ target = target[key];
139
+ }
140
+ target[path[path.length - 1]] = value;
141
+ return true;
142
+ }
143
+ function readPropertyValue(go, componentName, path) {
144
+ const comp = go.getComponent(componentName);
145
+ if (!comp || !path.length)
146
+ return undefined;
147
+ let target = comp;
148
+ for (const key of path) {
149
+ if (target === null || target === undefined || typeof target !== 'object') {
150
+ return undefined;
151
+ }
152
+ target = target[key];
153
+ }
154
+ return target;
155
+ }
156
+
157
+ const TRANSFORM_VECTOR_KEYS = new Set([
158
+ 'position',
159
+ 'size',
160
+ 'origin',
161
+ 'anchor',
162
+ 'scale',
163
+ 'skew',
164
+ ]);
165
+ function asNumber(value) {
166
+ if (typeof value === 'number' && Number.isFinite(value)) {
167
+ return value;
168
+ }
169
+ if (typeof value === 'string' && value.trim() !== '') {
170
+ const next = Number(value);
171
+ return Number.isFinite(next) ? next : undefined;
172
+ }
173
+ return undefined;
174
+ }
175
+ function syncPhysicsBodyFromTransform(go) {
176
+ const physics = go.getComponent('Physics');
177
+ if (!physics?.body) {
178
+ return;
179
+ }
180
+ const { x, y } = go.transform.position;
181
+ if (physics.Body?.setPosition) {
182
+ physics.Body.setPosition(physics.body, { x, y });
183
+ return;
184
+ }
185
+ physics.body.position.x = x;
186
+ physics.body.position.y = y;
187
+ }
188
+ function syncMoverOriginFromTransform(go) {
189
+ const mover = go.getComponent('PlatformerMover');
190
+ if (!mover) {
191
+ return;
192
+ }
193
+ mover.originX = go.transform.position.x;
194
+ mover.originY = go.transform.position.y;
195
+ }
196
+ function applyTransformProperty(go, comp, path, value) {
197
+ const transform = go.transform ?? comp;
198
+ const transformRecord = transform;
199
+ if (path.length === 1 && path[0] === 'rotation') {
200
+ const next = asNumber(value);
201
+ if (next === undefined) {
202
+ return false;
203
+ }
204
+ transform.rotation = next;
205
+ return true;
206
+ }
207
+ if (path.length === 2 && TRANSFORM_VECTOR_KEYS.has(path[0])) {
208
+ const groupKey = path[0];
209
+ const leafKey = path[1];
210
+ const next = asNumber(value);
211
+ if (next === undefined) {
212
+ return false;
213
+ }
214
+ const group = transformRecord[groupKey];
215
+ if (!group || typeof group !== 'object') {
216
+ return false;
217
+ }
218
+ group[leafKey] = next;
219
+ if (groupKey === 'position') {
220
+ syncPhysicsBodyFromTransform(go);
221
+ syncMoverOriginFromTransform(go);
222
+ }
223
+ return true;
224
+ }
225
+ return false;
226
+ }
227
+ function applyTextProperty(_go, comp, path, value) {
228
+ const textComp = comp;
229
+ if (path.length === 1 && path[0] === 'text') {
230
+ textComp.text = String(value ?? '');
231
+ return true;
232
+ }
233
+ if (path.length === 2 && path[0] === 'style') {
234
+ const styleKey = path[1];
235
+ const nextStyle = { ...(textComp.style ?? {}), [styleKey]: value };
236
+ textComp.style = nextStyle;
237
+ return true;
238
+ }
239
+ return false;
240
+ }
241
+ function applySoundProperty(_go, comp, path, value) {
242
+ const soundComp = comp;
243
+ if ((path.length === 1 && path[0] === 'volume') ||
244
+ (path.length === 2 && path[0] === 'config' && path[1] === 'volume')) {
245
+ const next = asNumber(value);
246
+ if (next === undefined) {
247
+ return false;
248
+ }
249
+ soundComp.volume = next;
250
+ return true;
251
+ }
252
+ return false;
253
+ }
254
+ const APPLY_HOOKS = {
255
+ Transform: applyTransformProperty,
256
+ Text: applyTextProperty,
257
+ Sound: applySoundProperty,
258
+ };
259
+ function applyPropertyWithHooks(go, componentName, path, value) {
260
+ const comp = go.getComponent(componentName);
261
+ if (!comp || !path.length) {
262
+ return false;
263
+ }
264
+ const hook = APPLY_HOOKS[componentName];
265
+ if (hook?.(go, comp, path, value)) {
266
+ return true;
267
+ }
268
+ return applyPropertyValue(go, componentName, path, value);
269
+ }
270
+
30
271
  const OUTLINE_GO_NAME = '__combosDevelopmentToolOutline';
31
272
  /**
32
273
  * **Defaults:** `scope: 'scene'`, **off** at start (no tap hijack until enabled).
@@ -37,7 +278,7 @@ const OUTLINE_GO_NAME = '__combosDevelopmentToolOutline';
37
278
  *
38
279
  * With `scope: 'scene'`, while enabled: `game.emit(COMBOS_DEVELOPMENT_TOOL_REFRESH)`, `window` event `combos-development-tool:refresh`, or `postMessage({ type: 'combos-development-tool:refresh' })` to re-bind after adding/removing objects.
39
280
  */
40
- let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engine.System {
281
+ let CombosDevelopmentToolSystem$1 = class CombosDevelopmentToolSystem extends engine.System {
41
282
  constructor() {
42
283
  super(...arguments);
43
284
  this.postMessageOrigin = '*';
@@ -50,6 +291,7 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
50
291
  /** We added `Event` for hit-testing; remove on teardown when disabling / releaseTap. */
51
292
  this.injectedEvents = new Set();
52
293
  this.needsSceneRescan = false;
294
+ this.needsTaggedRescan = false;
53
295
  this.onWindowSet = (e) => {
54
296
  const d = e.detail;
55
297
  if (d && typeof d.enabled === 'boolean') {
@@ -66,6 +308,9 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
66
308
  else if (d.type === COMBOS_DEVELOPMENT_TOOL_REFRESH) {
67
309
  this.requestSceneRescan();
68
310
  }
311
+ else if (d.type === COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY) {
312
+ this.handleApplyProperty(d);
313
+ }
69
314
  };
70
315
  this.onGameSet = (payload) => {
71
316
  if (payload && typeof payload.enabled === 'boolean') {
@@ -93,6 +338,9 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
93
338
  if (this.enabled && this.scope === 'scene') {
94
339
  this.needsSceneRescan = true;
95
340
  }
341
+ else if (this.enabled && this.scope === 'tagged') {
342
+ this.needsTaggedRescan = true;
343
+ }
96
344
  }
97
345
  onDestroy() {
98
346
  if (typeof window !== 'undefined') {
@@ -112,22 +360,27 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
112
360
  this.enabled = on;
113
361
  if (!on) {
114
362
  this.clearSelection();
115
- if (this.scope === 'scene') {
116
- this.detachAllTaps();
117
- }
363
+ this.detachAllTaps();
118
364
  }
119
365
  else if (this.scope === 'scene') {
120
366
  this.needsSceneRescan = true;
121
367
  }
368
+ else {
369
+ this.needsTaggedRescan = true;
370
+ }
122
371
  }
123
372
  get isEnabled() {
124
373
  return this.enabled;
125
374
  }
126
375
  update(e) {
127
- if (this.scope === 'scene' && this.enabled && this.needsSceneRescan) {
376
+ if (this.enabled && this.needsSceneRescan && this.scope === 'scene') {
128
377
  this.needsSceneRescan = false;
129
378
  this.attachSceneTree();
130
379
  }
380
+ if (this.enabled && this.needsTaggedRescan && this.scope === 'tagged') {
381
+ this.needsTaggedRescan = false;
382
+ this.attachTaggedObjects();
383
+ }
131
384
  for (const go of [...this.tapOwners.values()]) {
132
385
  if (go.destroyed) {
133
386
  this.releaseTap(go);
@@ -164,9 +417,29 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
164
417
  }
165
418
  /** Re-bind scene taps when `scope === 'scene'` (e.g. after dynamic spawn). */
166
419
  requestSceneRescan() {
167
- if (this.scope !== 'scene' || !this.enabled)
420
+ if (!this.enabled)
168
421
  return;
169
- this.needsSceneRescan = true;
422
+ if (this.scope === 'scene') {
423
+ this.needsSceneRescan = true;
424
+ }
425
+ else {
426
+ this.needsTaggedRescan = true;
427
+ }
428
+ }
429
+ attachTaggedObjects() {
430
+ const list = [];
431
+ for (const tr of this.game.scene.transform.children) {
432
+ this.collectGameObjects(tr.gameObject, list);
433
+ }
434
+ for (const go of list) {
435
+ if (go.name === OUTLINE_GO_NAME)
436
+ continue;
437
+ if (go === this.outlineGo)
438
+ continue;
439
+ if (!go.getComponent(CombosDevelopmentToolTarget))
440
+ continue;
441
+ this.ensureTap(go);
442
+ }
170
443
  }
171
444
  attachSceneTree() {
172
445
  this.detachAllTaps();
@@ -207,7 +480,7 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
207
480
  }
208
481
  if (this.tapHandlers.has(go.id))
209
482
  return;
210
- const handler = () => this.onSelect(go);
483
+ const handler = (payload) => this.onSelect(go, payload);
211
484
  this.tapHandlers.set(go.id, handler);
212
485
  this.tapOwners.set(go.id, go);
213
486
  ev.on('tap', handler);
@@ -241,7 +514,7 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
241
514
  go.addComponent(new pluginRendererGraphics.Graphics());
242
515
  this.outlineGo = go;
243
516
  }
244
- onSelect(go) {
517
+ onSelect(go, tap) {
245
518
  if (!this.enabled)
246
519
  return;
247
520
  this.ensureOutline();
@@ -252,11 +525,91 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
252
525
  this.outlineGo.remove();
253
526
  }
254
527
  go.addChild(this.outlineGo);
255
- const payload = this.serializeGameObject(go);
528
+ const snapshot = buildGameObjectSnapshot(go);
529
+ const payload = {
530
+ type: COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED,
531
+ snapshot,
532
+ pointer: tap?.data?.position ?? null,
533
+ };
256
534
  if (typeof window !== 'undefined' && window.parent && window.parent !== window) {
257
535
  window.parent.postMessage(payload, this.postMessageOrigin);
258
536
  }
259
537
  }
538
+ findGameObjectById(id) {
539
+ const stack = [];
540
+ for (const tr of this.game.scene.transform.children) {
541
+ this.collectGameObjects(tr.gameObject, stack);
542
+ }
543
+ return stack.find(go => go.id === id) ?? null;
544
+ }
545
+ findGameObjectBySource(source) {
546
+ const stack = [];
547
+ for (const tr of this.game.scene.transform.children) {
548
+ this.collectGameObjects(tr.gameObject, stack);
549
+ }
550
+ const wantFile = source.file.trim();
551
+ const wantAnchor = (source.anchor ?? '').trim();
552
+ if (!wantAnchor) {
553
+ return null;
554
+ }
555
+ for (const go of stack) {
556
+ const anchor = readSourceAnchor(go);
557
+ if (!anchor || anchor.file !== wantFile) {
558
+ continue;
559
+ }
560
+ const resolvedAnchor = anchor.anchor ?? go.name;
561
+ if (resolvedAnchor === wantAnchor) {
562
+ return go;
563
+ }
564
+ }
565
+ return null;
566
+ }
567
+ resolveApplyTarget(payload) {
568
+ if (typeof payload.gameObjectId === 'number') {
569
+ const byId = this.findGameObjectById(payload.gameObjectId);
570
+ if (byId) {
571
+ return byId;
572
+ }
573
+ }
574
+ const file = payload.source?.file?.trim();
575
+ const anchor = payload.source?.anchor?.trim();
576
+ if (!file || !anchor) {
577
+ return null;
578
+ }
579
+ return this.findGameObjectBySource({ file, anchor });
580
+ }
581
+ handleApplyProperty(payload) {
582
+ const componentName = payload.componentName;
583
+ const path = payload.path;
584
+ const hasId = typeof payload.gameObjectId === 'number';
585
+ const hasSource = Boolean(payload.source?.file?.trim()) && Boolean(payload.source?.anchor?.trim());
586
+ if ((!hasId && !hasSource) ||
587
+ typeof componentName !== 'string' ||
588
+ !Array.isArray(path) ||
589
+ path.length === 0) {
590
+ return;
591
+ }
592
+ const go = this.resolveApplyTarget(payload);
593
+ if (!go)
594
+ return;
595
+ const applied = applyPropertyWithHooks(go, componentName, path, payload.value);
596
+ if (!applied)
597
+ return;
598
+ if (this.selected?.id === go.id) {
599
+ this.postSnapshotUpdate(go);
600
+ }
601
+ }
602
+ postSnapshotUpdate(go) {
603
+ const snapshot = buildGameObjectSnapshot(go);
604
+ if (typeof window !== 'undefined' && window.parent && window.parent !== window) {
605
+ window.parent.postMessage({
606
+ type: COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED,
607
+ snapshot,
608
+ pointer: null,
609
+ source: 'property-applied',
610
+ }, this.postMessageOrigin);
611
+ }
612
+ }
260
613
  clearSelection() {
261
614
  this.selected = null;
262
615
  if (this.outlineGo?.parent) {
@@ -267,33 +620,30 @@ let CombosDevelopmentToolSystem = class CombosDevelopmentToolSystem extends engi
267
620
  gfx.graphics.clear();
268
621
  }
269
622
  }
270
- serializeGameObject(go) {
271
- const tag = go.getComponent(CombosDevelopmentToolTarget);
272
- const pos = go.transform.position;
273
- const size = go.transform.size;
274
- return {
275
- type: COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED,
276
- gameObject: {
277
- name: go.name,
278
- id: go.id,
279
- position: { x: pos.x, y: pos.y },
280
- size: { width: size.width, height: size.height },
281
- rotation: go.transform.rotation,
282
- payload: tag?.payload ?? {},
283
- },
284
- };
285
- }
286
623
  };
287
- CombosDevelopmentToolSystem = tslib.__decorate([
624
+ CombosDevelopmentToolSystem$1 = tslib.__decorate([
288
625
  engine.decorators.componentObserver({
289
626
  CombosDevelopmentToolTarget: [],
290
627
  })
291
- ], CombosDevelopmentToolSystem);
292
- var CombosDevelopmentToolSystem_default = CombosDevelopmentToolSystem;
628
+ ], CombosDevelopmentToolSystem$1);
629
+ var CombosDevelopmentToolSystem = CombosDevelopmentToolSystem$1;
630
+
631
+ /** Auto-generated by scripts/build-package.mjs — do not edit. */
632
+ Object.assign(CombosDevelopmentToolSystem, {
633
+ packageName: "@combos-fun/plugin-development-tool",
634
+ packageVersion: "0.0.14",
635
+ });
293
636
 
637
+ exports.COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY = COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY;
294
638
  exports.COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED = COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED;
639
+ exports.COMBOS_DEVELOPMENT_TOOL_READY = COMBOS_DEVELOPMENT_TOOL_READY;
295
640
  exports.COMBOS_DEVELOPMENT_TOOL_REFRESH = COMBOS_DEVELOPMENT_TOOL_REFRESH;
296
641
  exports.COMBOS_DEVELOPMENT_TOOL_SET = COMBOS_DEVELOPMENT_TOOL_SET;
297
- exports.CombosDevelopmentToolSystem = CombosDevelopmentToolSystem_default;
642
+ exports.CombosDevelopmentToolSystem = CombosDevelopmentToolSystem;
298
643
  exports.CombosDevelopmentToolTarget = CombosDevelopmentToolTarget;
644
+ exports.applyPropertyValue = applyPropertyValue;
645
+ exports.applyPropertyWithHooks = applyPropertyWithHooks;
646
+ exports.buildGameObjectSnapshot = buildGameObjectSnapshot;
647
+ exports.readPropertyValue = readPropertyValue;
648
+ exports.readSourceAnchor = readSourceAnchor;
299
649
  //# sourceMappingURL=plugin-development-tool.cjs.js.map