You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

628 lines
24 KiB

3 days ago
(function (global) {
'use strict';
var WorkbenchAgent = global.PWAgent;
var U = global.PWAgentUtil;
var nodeId = U && U.nodeId;
var walk = U && U.walk;
if (!WorkbenchAgent) {
console.error('[调试工作台] agent-pick 需先加载 agent-core');
return;
}
3 days ago
function log() {
try {
var fn = console.log || console.info;
fn.apply(console, ['[检视]'].concat(Array.prototype.slice.call(arguments)));
} catch (e) { /* */ }
}
/** 检视模式:点选节点(Creator 场景拾取) */
3 days ago
WorkbenchAgent.prototype._setPickMode = function (mode) {
this._pickMode = mode === 'inspect' ? 'inspect' : 'game';
3 days ago
try {
document.documentElement.classList.toggle('pw-inspect', this._pickMode === 'inspect');
var root = document.getElementById('pw-root') || document.body;
if (root && root.classList) {
root.classList.toggle('pw-inspect', this._pickMode === 'inspect');
}
} catch (e) { /* */ }
this._bindCanvasPick(this._pickMode === 'inspect');
log('setMode', this._pickMode);
3 days ago
return this._pickMode;
};
3 days ago
/**
* 壳层 / 游戏内共用入口。
* 同一位置重复点击时,在重叠节点里从上到下循环切换。
* @param {{clientX:number, clientY:number, space?:string}} params
*/
WorkbenchAgent.prototype._pickAt = function (params) {
var clientX = params && Number(params.clientX);
var clientY = params && Number(params.clientY);
if (!isFinite(clientX) || !isFinite(clientY)) {
return { uuid: this._selectedUuid || '', ok: false, reason: 'bad-coord' };
}
var r = this._hitTestDetailed(clientX, clientY);
var uuid = r.uuid || '';
var cycleInfo = '';
// 同点循环:位置接近时在命中列表里从上到下轮换
if (r.hits && r.hits.length) {
uuid = this._cyclePick(clientX, clientY, r.hits);
if (this._pickCycleList && this._pickCycleList.length > 1) {
cycleInfo = ' [' + (this._pickCycleIndex + 1) + '/' + this._pickCycleList.length + ']';
}
}
log('pickAt', clientX, clientY, '→', uuid || '(none)', (r.method || '') + cycleInfo);
var res = this._selectNode(uuid);
res.method = r.method || '';
res.cycle = this._pickCycleList && this._pickCycleList.length
? { index: this._pickCycleIndex, total: this._pickCycleList.length, names: this._pickCycleList.map(function (h) { return h.name; }) }
: null;
res.debug = r.debug || null;
return res;
};
/**
* 同一坐标重复点击 → 在命中列表(上→下)中循环。
* 位置偏移超过阈值则重置,从最上层重新开始。
*/
WorkbenchAgent.prototype._cyclePick = function (clientX, clientY, hits) {
var EPS = 8;
if (!hits || !hits.length) return '';
this._pickCycleList = hits;
this._pickCycleX = clientX;
this._pickCycleY = clientY;
var sameSpot = this._pickCycleArmed &&
Math.abs(clientX - (this._pickCycleLastX || 0)) <= EPS &&
Math.abs(clientY - (this._pickCycleLastY || 0)) <= EPS;
this._pickCycleLastX = clientX;
this._pickCycleLastY = clientY;
this._pickCycleArmed = true;
if (!sameSpot) {
this._pickCycleIndex = 0;
return hits[0].uuid;
}
// 从当前选中节点的下一层开始
var idx = 0;
for (var i = 0; i < hits.length; i++) {
if (hits[i].uuid === this._selectedUuid) {
idx = (i + 1) % hits.length;
break;
}
}
// 若上一选中不在列表里,则从 0 起
this._pickCycleIndex = idx;
return hits[idx].uuid;
};
3 days ago
WorkbenchAgent.prototype._bindCanvasPick = function (on) {
3 days ago
var self = this;
if (!this._onPickDown) {
this._onPickDown = function (ev) {
if (self._pickMode !== 'inspect') return;
if (ev && ev.preventDefault) ev.preventDefault();
if (ev && ev.stopPropagation) ev.stopPropagation();
self._pickAt({ clientX: ev.clientX, clientY: ev.clientY, space: 'viewport' });
};
}
if (this._pickBoundEls && this._pickBoundEls.length) {
for (var i = 0; i < this._pickBoundEls.length; i++) {
this._pickBoundEls[i].removeEventListener('pointerdown', this._onPickDown, true);
this._pickBoundEls[i].removeEventListener('mousedown', this._onPickDown, true);
}
}
this._pickBoundEls = [];
if (!on || this._pickMode !== 'inspect') return;
var targets = this._pickEventTargets();
for (var j = 0; j < targets.length; j++) {
if (!targets[j]) continue;
targets[j].addEventListener('pointerdown', this._onPickDown, true);
targets[j].addEventListener('mousedown', this._onPickDown, true);
this._pickBoundEls.push(targets[j]);
}
window.addEventListener('pointerdown', this._onPickDown, true);
window.addEventListener('mousedown', this._onPickDown, true);
this._pickBoundEls.push(window);
log('bind pick', this._pickBoundEls.length);
};
WorkbenchAgent.prototype._pickEventTargets = function () {
var list = [];
var ids = ['GameCanvas', 'Cocos3dGameContainer', 'GameDiv', 'content', 'pw-center', 'pw-game-host'];
for (var i = 0; i < ids.length; i++) {
var el = document.getElementById(ids[i]);
if (el && list.indexOf(el) < 0) list.push(el);
}
var cv = global.cc && global.cc.game && global.cc.game.canvas;
if (cv && list.indexOf(cv) < 0) list.push(cv);
return list;
};
WorkbenchAgent.prototype._getCanvasEl = function () {
return document.getElementById('GameCanvas') ||
(global.cc && global.cc.game && global.cc.game.canvas) ||
null;
};
WorkbenchAgent.prototype._uiCamera = function () {
var cc = this.cc;
if (!cc) return null;
try {
var scene = cc.director && cc.director.getScene && cc.director.getScene();
if (scene && cc.UITransform) {
var canvasNode = null;
walk(scene, function (n) {
if (!canvasNode && n && n.name === 'Canvas') canvasNode = n;
});
if (canvasNode && canvasNode.getComponent && cc.Camera) {
var cam = canvasNode.getComponent(cc.Camera);
if (cam) return cam;
}
}
if (cc.director && cc.director.root && cc.director.root.batcher2D && cc.director.root.batcher2D.camera) {
return cc.director.root.batcher2D.camera;
}
if (cc.director && cc.director.root && cc.director.root.cameraList && cc.director.root.cameraList.length) {
var list = cc.director.root.cameraList;
for (var i = 0; i < list.length; i++) {
if (list[i] && (list[i].camera || list[i])._uiCamera !== false) {
return list[i].camera || list[i];
3 days ago
}
3 days ago
}
3 days ago
}
3 days ago
} catch (e) { /* */ }
return null;
};
WorkbenchAgent.prototype._canvasWorldBox = function () {
var cc = this.cc;
if (!cc || !cc.UITransform) return null;
var scene = cc.director && cc.director.getScene && cc.director.getScene();
if (!scene) return null;
var canvasNode = null;
walk(scene, function (n) {
if (!canvasNode && n && n.name === 'Canvas' && n.activeInHierarchy) canvasNode = n;
});
if (!canvasNode || !canvasNode.getComponent) return null;
var uit = canvasNode.getComponent(cc.UITransform);
if (!uit || !uit.getBoundingBoxToWorld) return null;
var box = null;
try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; }
if (!box || !(box.width > 0) || !(box.height > 0)) return null;
return { x: box.x, y: box.y, width: box.width, height: box.height };
};
WorkbenchAgent.prototype._nodeDepth = function (node) {
var d = 0;
var p = node && node.parent;
while (p) {
d += 1;
p = p.parent;
3 days ago
}
3 days ago
return d;
3 days ago
};
3 days ago
/** 把 client 点换算到 Cocos 屏幕坐标(原点左下、CSS 像素,不乘 DPR) */
WorkbenchAgent.prototype._toCocosScreen = function (clientX, clientY) {
var canvas = this._getCanvasEl();
if (!canvas) return null;
var rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return null;
return {
sx: clientX - rect.left,
sy: rect.height - (clientY - rect.top),
rect: rect,
};
};
/**
* 多策略命中,返回最优节点。
* @returns {{uuid:string, method:string, debug:object}}
*/
WorkbenchAgent.prototype._hitTestDetailed = function (clientX, clientY) {
3 days ago
var cc = this.cc;
3 days ago
var empty = { uuid: '', method: 'none', debug: null };
if (!cc || !cc.director) {
empty.method = 'no-cc';
return empty;
}
3 days ago
var scene = cc.director.getScene();
3 days ago
if (!scene) {
empty.method = 'no-scene';
return empty;
}
3 days ago
var stack = [];
3 days ago
walk(scene, function (n) {
if (n && n.activeInHierarchy) stack.push(n);
});
var cam = this._uiCamera();
var screen = this._toCocosScreen(clientX, clientY);
var cbox = this._canvasWorldBox();
var worlds = [];
if (screen && cbox) {
var u = (clientX - screen.rect.left) / screen.rect.width;
var v = (clientY - screen.rect.top) / screen.rect.height;
worlds.push({
x: cbox.x + u * cbox.width,
y: cbox.y + (1 - v) * cbox.height,
tag: 'canvasBox',
});
}
if (screen && cc.view && cc.view.getDesignResolutionSize) {
var d = cc.view.getDesignResolutionSize();
if (d && d.width) {
var u2 = (clientX - screen.rect.left) / screen.rect.width;
var v2 = (clientY - screen.rect.top) / screen.rect.height;
worlds.push({ x: u2 * d.width, y: (1 - v2) * d.height, tag: 'design' });
worlds.push({ x: (u2 - 0.5) * d.width, y: (0.5 - v2) * d.height, tag: 'center' });
}
}
var self = this;
var uiCount = 0;
var samples = [];
var hits = [];
var hitUuids = {};
var getBox = function (uit) {
var box = null;
try {
if (uit && uit.getBoundingBoxToWorld) box = uit.getBoundingBoxToWorld();
} catch (e) { box = null; }
if (!box) return null;
var x = Number(box.x);
var y = Number(box.y);
var w = Number(box.width);
var h = Number(box.height);
if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h)) return null;
if (w < 0) { x += w; w = -w; }
if (h < 0) { y += h; h = -h; }
return { x: x, y: y, width: w, height: h };
};
var siblingIndex = function (node) {
var p = node && node.parent;
if (!p || !p.children) return 0;
var idx = p.children.indexOf ? p.children.indexOf(node) : 0;
return idx < 0 ? 0 : idx;
};
var addHit = function (node, method, area) {
var id = nodeId(node);
if (hitUuids[id]) return;
hitUuids[id] = true;
hits.push({
uuid: id,
name: node.name || '',
isCanvas: (node.name || '') === 'Canvas',
depth: self._nodeDepth(node),
sIndex: siblingIndex(node),
area: area || 0,
method: method,
});
};
for (var s = 0; s < stack.length; s++) {
var node = stack[s];
if (!node || !node.getComponent) continue;
var uit = null;
try {
uit = (cc.UITransform && node.getComponent(cc.UITransform)) ||
node.getComponent('cc.UITransform') ||
node.getComponent('UITransform') ||
null;
} catch (e1) { uit = null; }
if (!uit) {
// 兜底:任何带 getBoundingBoxToWorld 的组件
var comps = node.components || [];
for (var ci = 0; ci < comps.length; ci++) {
if (comps[ci] && typeof comps[ci].getBoundingBoxToWorld === 'function') {
uit = comps[ci];
break;
}
}
}
3 days ago
if (!uit) continue;
3 days ago
uiCount += 1;
var box = getBox(uit);
3 days ago
if (!box) continue;
3 days ago
var area = box.width * box.height;
if (samples.length < 5) {
samples.push({
name: node.name || '?',
box: box,
});
}
// A: hitTest(camera, screen)
if (cam && screen && typeof uit.hitTest === 'function') {
try {
var sp = cc.Vec2 ? new cc.Vec2(screen.sx, screen.sy) : { x: screen.sx, y: screen.sy };
if (uit.hitTest(cam, sp)) addHit(node, 'hitTest', area);
} catch (e2) { /* */ }
}
// B: 世界 AABB(含 Rect.contains)
for (var w = 0; w < worlds.length; w++) {
var W = worlds[w];
var inside = false;
try {
var raw = uit.getBoundingBoxToWorld && uit.getBoundingBoxToWorld();
if (raw && typeof raw.contains === 'function' && cc.Vec2) {
if (raw.contains(new cc.Vec2(W.x, W.y))) inside = true;
}
} catch (e3) { /* */ }
if (!inside) {
inside = (W.x >= box.x && W.x <= box.x + box.width && W.y >= box.y && W.y <= box.y + box.height);
}
if (inside) {
addHit(node, 'aabb:' + W.tag, area);
break;
}
3 days ago
}
}
3 days ago
if (!hits.length) {
empty.method = uiCount ? 'miss' : 'no-uitransform';
empty.debug = {
worlds: worlds,
screen: screen ? { sx: screen.sx, sy: screen.sy, w: screen.rect.width, h: screen.rect.height, left: screen.rect.left, top: screen.rect.top } : null,
cbox: cbox,
nodes: stack.length,
uiCount: uiCount,
samples: samples,
cam: !!cam,
};
log('miss', JSON.stringify(empty.debug));
return empty;
}
// 视觉层级:上→下(更深、更后绘制的兄弟优先)
hits.sort(function (a, b) {
if (a.depth !== b.depth) return b.depth - a.depth;
if (a.sIndex !== b.sIndex) return b.sIndex - a.sIndex;
return a.area - b.area;
});
return {
uuid: hits[0].uuid,
method: hits[0].method,
hits: hits,
debug: {
best: hits[0].name,
hits: hits.length,
worlds: worlds,
cbox: cbox,
},
};
};
/** 兼容旧调用 */
WorkbenchAgent.prototype._hitTest = function (clientX, clientY) {
return this._hitTestDetailed(clientX, clientY).uuid || '';
3 days ago
};
3 days ago
/** 取 UITransform(多种查找方式,与命中逻辑一致) */
WorkbenchAgent.prototype._getUiTransform = function (node) {
var cc = this.cc;
if (!cc || !node || !node.getComponent) return null;
try {
var uit = (cc.UITransform && node.getComponent(cc.UITransform)) ||
node.getComponent('cc.UITransform') ||
node.getComponent('UITransform') ||
null;
if (uit && uit.getBoundingBoxToWorld) return uit;
} catch (e) { /* */ }
var comps = node.components || [];
for (var i = 0; i < comps.length; i++) {
if (comps[i] && typeof comps[i].getBoundingBoxToWorld === 'function') return comps[i];
}
return null;
};
3 days ago
WorkbenchAgent.prototype._ensureOverlay = function () {
var el = document.getElementById('pw-pick-overlay');
if (!el) {
el = document.createElement('div');
el.id = 'pw-pick-overlay';
el.className = 'pw-pick-overlay';
el.hidden = true;
// fixed 相对游戏视口,挂 body 避免父级 transform 干扰
(document.body || document.documentElement).appendChild(el);
3 days ago
}
this._overlay = el;
};
3 days ago
WorkbenchAgent.prototype._updateOverlay = function () {
this._ensureOverlay();
var node = this._findNode(this._selectedUuid);
var overlay = this._overlay;
if (!overlay) return;
if (!node) {
overlay.hidden = true;
return;
}
var box = this._nodeClientRect(node);
if (!box) {
overlay.hidden = true;
return;
}
// 挂在 documentElement 上用 viewport 坐标(fixed),与 _nodeClientRect 同一套
3 days ago
overlay.hidden = false;
overlay.style.position = 'fixed';
overlay.style.left = box.left + 'px';
overlay.style.top = box.top + 'px';
3 days ago
overlay.style.width = box.width + 'px';
overlay.style.height = box.height + 'px';
overlay.style.zIndex = '99999';
3 days ago
overlay.style.display = 'block';
overlay.style.pointerEvents = 'none';
3 days ago
};
/**
* 节点世界 AABB → 浏览器视口矩形(游戏 iframe 内)。
* 主路径:相机 worldToScreen;回退:Canvas 盒 UV 映射。
*/
3 days ago
WorkbenchAgent.prototype._nodeClientRect = function (node) {
var cc = this.cc;
3 days ago
var canvas = this._getCanvasEl();
3 days ago
if (!cc || !canvas || !node) return null;
if (node.isValid === false) return null;
var uit = this._getUiTransform(node);
3 days ago
if (!uit || !uit.getBoundingBoxToWorld) return null;
var box = null;
try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; }
3 days ago
if (!box) return null;
3 days ago
var bx = Number(box.x);
var by = Number(box.y);
var bw = Number(box.width);
var bh = Number(box.height);
if (!isFinite(bx) || !isFinite(by) || !isFinite(bw) || !isFinite(bh)) return null;
if (bw < 0) { bx += bw; bw = -bw; }
if (bh < 0) { by += bh; bh = -bh; }
3 days ago
var rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return null;
3 days ago
var corners = [
3 days ago
{ x: bx, y: by },
{ x: bx + bw, y: by },
{ x: bx + bw, y: by + bh },
{ x: bx, y: by + bh },
3 days ago
];
// 1) 相机投影(最准,自动处理缩放/适配)
var cam = this._uiCamera();
if (cam && typeof cam.worldToScreen === 'function') {
var xs = [];
var ys = [];
var ok = true;
try {
for (var i = 0; i < 4; i++) {
var sp = cc.Vec3 ? new cc.Vec3() : { x: 0, y: 0, z: 0 };
var wp = cc.Vec3
? new cc.Vec3(corners[i].x, corners[i].y, 0)
: { x: corners[i].x, y: corners[i].y, z: 0 };
cam.worldToScreen(sp, wp);
// worldToScreen:原点多在画布左下,单位为画布像素
var sx = Number(sp.x);
var sy = Number(sp.y);
if (!isFinite(sx) || !isFinite(sy)) { ok = false; break; }
// 画布缓冲像素 → CSS 视口
var cx = rect.left + (sx / Math.max(1, canvas.width)) * rect.width;
var cy = rect.top + rect.height - (sy / Math.max(1, canvas.height)) * rect.height;
xs.push(cx);
ys.push(cy);
}
} catch (e2) { ok = false; }
if (ok && xs.length === 4) {
var L = Math.min.apply(null, xs);
var R = Math.max.apply(null, xs);
var T = Math.min.apply(null, ys);
var B = Math.max.apply(null, ys);
if (isFinite(L) && isFinite(T) && R > L && B > T) {
return {
left: L,
top: T,
right: R,
bottom: B,
width: Math.max(2, R - L),
height: Math.max(2, B - T),
};
}
}
}
// 2) Canvas 世界盒 UV 映射
var cbox = this._canvasWorldBox();
var vw = Math.max(1, (cbox && cbox.width) || rect.width);
var vh = Math.max(1, (cbox && cbox.height) || rect.height);
var ox = cbox ? cbox.x : 0;
var oy = cbox ? cbox.y : 0;
var xs2 = [];
var ys2 = [];
for (var j = 0; j < 4; j++) {
var u = (corners[j].x - ox) / vw;
var v = 1 - (corners[j].y - oy) / vh;
xs2.push(rect.left + u * rect.width);
ys2.push(rect.top + v * rect.height);
3 days ago
}
var left = Math.min.apply(null, xs2);
var right = Math.max.apply(null, xs2);
var top = Math.min.apply(null, ys2);
var bottom = Math.max.apply(null, ys2);
3 days ago
return {
left: left,
top: top,
right: right,
bottom: bottom,
width: Math.max(2, right - left),
height: Math.max(2, bottom - top),
};
3 days ago
};
WorkbenchAgent.prototype._getOverlayRect = function () {
3 days ago
var node = this._findNode(this._selectedUuid);
var rect = node ? this._nodeClientRect(node) : null;
var extra = this._getDragState(node);
if (rect) {
return {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
right: rect.right,
bottom: rect.bottom,
uuid: this._selectedUuid || '',
pos: extra.pos,
scale: extra.scale,
};
3 days ago
}
3 days ago
var overlay = this._overlay;
if (!overlay || overlay.hidden) return null;
return {
left: parseFloat(overlay.style.left) || 0,
top: parseFloat(overlay.style.top) || 0,
width: parseFloat(overlay.style.width) || 0,
height: parseFloat(overlay.style.height) || 0,
uuid: this._selectedUuid || '',
pos: extra.pos,
scale: extra.scale,
3 days ago
};
};
/** 拖拽所需的节点位置与「屏幕像素→本地坐标」缩放 */
WorkbenchAgent.prototype._getDragState = function (node) {
var pos = { x: 0, y: 0, z: 0 };
if (node) {
try {
var p = node.getPosition ? node.getPosition() : node.position;
if (p) pos = { x: p.x || 0, y: p.y || 0, z: p.z || 0 };
} catch (e) { /* */ }
}
var canvas = this._getCanvasEl();
var cbox = this._canvasWorldBox();
var design = this.cc && this.cc.view && this.cc.view.getDesignResolutionSize
? this.cc.view.getDesignResolutionSize()
: null;
var w = (cbox && cbox.width) || (design && design.width) || 750;
var h = (cbox && cbox.height) || (design && design.height) || 1600;
var rect = canvas && canvas.getBoundingClientRect ? canvas.getBoundingClientRect() : null;
var sx = rect && rect.width ? w / rect.width : 1;
var sy = rect && rect.height ? h / rect.height : 1;
return { pos: pos, scale: { x: sx, y: sy } };
};
3 days ago
})(window);