(function (global) { 'use strict'; /** * Canvas pick / inspect mode without project code. * @constructor */ function NodePickController() { this.mode = 'game'; this._cc = null; this._selected = null; this._onSelect = null; this._onMode = null; this._boundDown = this._onPointerDown.bind(this); this._canvas = null; this._overlay = null; this._raf = 0; } /** @param {*} cc */ NodePickController.prototype.bindEngine = function (cc) { this._cc = cc; this._canvas = document.getElementById('GameCanvas'); this._ensureOverlay(); this.setMode('game'); }; /** 挂到 pw-root,用 fixed 定位,避免被预览容器 overflow 裁切 */ NodePickController.prototype._ensureOverlay = function () { var overlay = document.getElementById('pw-pick-overlay'); var root = document.getElementById('pw-root'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'pw-pick-overlay'; overlay.className = 'pw-pick-overlay'; overlay.hidden = true; } if (root && overlay.parentNode !== root) root.appendChild(overlay); this._overlay = overlay; }; /** @param {function(*):void} cb */ NodePickController.prototype.onSelect = function (cb) { this._onSelect = cb; }; /** @param {function(string):void} cb */ NodePickController.prototype.onModeChange = function (cb) { this._onMode = cb; }; /** @returns {*} */ NodePickController.prototype.getSelected = function () { return this._selected; }; /** * @param {string} mode game|inspect */ NodePickController.prototype.setMode = function (mode) { this.mode = mode === 'inspect' ? 'inspect' : 'game'; var root = document.getElementById('pw-root'); if (root) root.classList.toggle('pw-inspect', this.mode === 'inspect'); var gameBtn = document.getElementById('pw-mode-game'); var inspectBtn = document.getElementById('pw-mode-inspect'); if (gameBtn) gameBtn.classList.toggle('active', this.mode === 'game'); if (inspectBtn) inspectBtn.classList.toggle('active', this.mode === 'inspect'); if (!this._canvas) return; if (this.mode === 'inspect') { this._canvas.addEventListener('pointerdown', this._boundDown, true); } else { this._canvas.removeEventListener('pointerdown', this._boundDown, true); } if (this._selected) this._startOverlayLoop(); else { this._stopOverlayLoop(); if (this._overlay) this._overlay.hidden = true; } if (this._onMode) this._onMode(this.mode); }; /** @param {*} node */ NodePickController.prototype.select = function (node) { this._selected = node || null; this._ensureOverlay(); if (this._selected) this._startOverlayLoop(); else { this._stopOverlayLoop(); if (this._overlay) this._overlay.hidden = true; } this._updateOverlay(); if (this._onSelect) this._onSelect(this._selected); }; NodePickController.prototype._onPointerDown = function (ev) { if (this.mode !== 'inspect') return; ev.preventDefault(); ev.stopPropagation(); var node = this._hitTest(ev.clientX, ev.clientY); this.select(node); }; NodePickController.prototype._hitTest = function (clientX, clientY) { var cc = this._cc; var canvas = this._canvas; if (!cc || !canvas) return null; var scene = cc.director.getScene(); if (!scene) return null; var stack = []; this._walk(scene, function (n) { stack.push(n); }); for (var i = stack.length - 1; i >= 0; i--) { var node = stack[i]; if (!node || !node.activeInHierarchy) continue; var uit = node.getComponent && node.getComponent(cc.UITransform); if (!uit) continue; var box = this._getNodeClientRect(node); if (!box) continue; if (clientX >= box.left && clientX <= box.right && clientY >= box.top && clientY <= box.bottom) { return node; } } return null; }; NodePickController.prototype._walk = function (node, fn) { if (!node) return; fn(node); var children = node.children; if (!children) return; for (var i = 0; i < children.length; i++) this._walk(children[i], fn); }; NodePickController.prototype._startOverlayLoop = function () { var self = this; this._stopOverlayLoop(); var tick = function () { self._updateOverlay(); self._raf = requestAnimationFrame(tick); }; this._raf = requestAnimationFrame(tick); }; NodePickController.prototype._stopOverlayLoop = function () { if (this._raf) cancelAnimationFrame(this._raf); this._raf = 0; }; /** * UI 节点包围盒 → 浏览器 client 坐标。 * 使用可见区域中心原点映射(与竖屏 UI Canvas 一致),避免 worldToScreen 在自定义帧尺寸下偏移。 * @param {*} node */ NodePickController.prototype._getNodeClientRect = function (node) { var cc = this._cc; var canvas = this._canvas; if (!cc || !canvas || !node || !node.isValid) return null; var uit = node.getComponent && node.getComponent(cc.UITransform); if (!uit) return null; var canvasRect = canvas.getBoundingClientRect(); if (canvasRect.width < 2 || canvasRect.height < 2) return null; var corners = null; if (typeof uit.getBoundingBoxToWorld === 'function') { var box = uit.getBoundingBoxToWorld(); if (box && box.width >= 0 && box.height >= 0) { 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 }, ]; } } if (!corners) corners = this._localCornersToWorld(uit); if (!corners) return null; var vs = cc.view && typeof cc.view.getVisibleSize === 'function' ? cc.view.getVisibleSize() : { width: canvasRect.width, height: canvasRect.height }; var vw = Math.max(1, vs.width); var vh = Math.max(1, vs.height); var xs = []; var ys = []; for (var i = 0; i < corners.length; i++) { // UI 世界坐标:原点在可见区域中心,Y 向上 var nx = corners[i].x / vw + 0.5; var ny = 0.5 - corners[i].y / vh; xs.push(canvasRect.left + nx * canvasRect.width); ys.push(canvasRect.top + ny * canvasRect.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), }; }; NodePickController.prototype._localCornersToWorld = function (uit) { var cc = this._cc; var w = uit.width; var h = uit.height; var ax = uit.anchorX != null ? uit.anchorX : (uit.anchorPoint ? uit.anchorPoint.x : 0.5); var ay = uit.anchorY != null ? uit.anchorY : (uit.anchorPoint ? uit.anchorPoint.y : 0.5); var locals = [ { x: -ax * w, y: -ay * h }, { x: (1 - ax) * w, y: -ay * h }, { x: (1 - ax) * w, y: (1 - ay) * h }, { x: -ax * w, y: (1 - ay) * h }, ]; if (typeof uit.convertToWorldSpaceAR !== 'function' || !cc.Vec3) return null; var out = []; for (var i = 0; i < 4; i++) { out.push(uit.convertToWorldSpaceAR(new cc.Vec3(locals[i].x, locals[i].y, 0))); } return out; }; NodePickController.prototype._updateOverlay = function () { var overlay = this._overlay; var node = this._selected; var cc = this._cc; if (!overlay) return; if (!node || !node.isValid || !cc) { overlay.hidden = true; return; } var box = this._getNodeClientRect(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'; }; global.PWNodePick = NodePickController; })(window);