(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; } function log() { try { var fn = console.log || console.info; fn.apply(console, ['[检视]'].concat(Array.prototype.slice.call(arguments))); } catch (e) { /* */ } } /** 检视模式:点选节点(Creator 场景拾取) */ WorkbenchAgent.prototype._setPickMode = function (mode) { this._pickMode = mode === 'inspect' ? 'inspect' : 'game'; 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); return this._pickMode; }; /** * 壳层 / 游戏内共用入口。 * 同一位置重复点击时,在重叠节点里从上到下循环切换。 * @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; }; WorkbenchAgent.prototype._bindCanvasPick = function (on) { 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]; } } } } 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; } return d; }; /** 把 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) { var cc = this.cc; var empty = { uuid: '', method: 'none', debug: null }; if (!cc || !cc.director) { empty.method = 'no-cc'; return empty; } var scene = cc.director.getScene(); if (!scene) { empty.method = 'no-scene'; return empty; } var stack = []; 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; } } } if (!uit) continue; uiCount += 1; var box = getBox(uit); if (!box) continue; 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; } } } 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 || ''; }; 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; document.body.appendChild(el); } this._overlay = el; }; WorkbenchAgent.prototype._nodeClientRect = function (node) { var cc = this.cc; var canvas = this._getCanvasEl(); if (!cc || !canvas || !node || !node.isValid) return null; var uit = node.getComponent && node.getComponent(cc.UITransform); if (!uit || !uit.getBoundingBoxToWorld) return null; var box = null; try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; } if (!box) return null; var rect = canvas.getBoundingClientRect(); var cbox = this._canvasWorldBox(); var vw, vh, ox, oy; if (cbox) { vw = Math.max(1, cbox.width); vh = Math.max(1, cbox.height); ox = cbox.x; oy = cbox.y; } else { var design = cc.view && cc.view.getDesignResolutionSize ? cc.view.getDesignResolutionSize() : null; vw = Math.max(1, (design && design.width) || rect.width); vh = Math.max(1, (design && design.height) || rect.height); ox = 0; oy = 0; } var corners = [ { x: box.x, y: box.y }, { x: box.x + box.width, y: box.y }, { x: box.x + box.width, y: box.y + box.height }, { x: box.x, y: box.y + box.height }, ]; var xs = []; var ys = []; for (var i = 0; i < 4; i++) { var u = (corners[i].x - ox) / vw; var v = 1 - (corners[i].y - oy) / vh; xs.push(rect.left + u * rect.width); ys.push(rect.top + v * rect.height); } var left = Math.min.apply(null, xs); var right = Math.max.apply(null, xs); var top = Math.min.apply(null, ys); var bottom = Math.max.apply(null, ys); return { left: left, top: top, right: right, bottom: bottom, width: Math.max(2, right - left), height: Math.max(2, bottom - top), }; }; WorkbenchAgent.prototype._updateOverlay = function () { 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; } overlay.hidden = false; overlay.style.position = 'fixed'; overlay.style.left = box.left + 'px'; overlay.style.top = box.top + 'px'; overlay.style.width = box.width + 'px'; overlay.style.height = box.height + 'px'; }; WorkbenchAgent.prototype._getOverlayRect = function () { 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, }; }; })(window);