//********************* // create by 流云 // time: Thu Nov 13 2025 // desc: //********************* import { _decorator, Node, EventTouch, UITransform, tween, Mask, Graphics, Color } from 'cc'; import { Auto_NewGuideMask } from './Auto_NewGuideMask'; import MTools from 'db://assets/mx/tools/MTools'; const { ccclass, property } = _decorator; @ccclass('UINewGuideMask') export class UINewGuideMask extends Auto_NewGuideMask { registNode = null graphics = null finishCallBack = null nodeArray: Node[] = []; labArray: string[] = []; isPlayAnim = true blackOpacity = 255 //不需要点击拦截 hideSpineshouzhi = true /** 镂空圆角半径(像素),不超过短边一半 */ holeCornerRadius = 18 /** 边缘虚化描边最大宽度 */ holeFeatherMaxLine = 5 /** 虚化层数 */ holeFeatherLayers = 8 _holeBorderGraphics: Graphics = null onLoad(): void { super.onLoad(); } onEnable(): void { super.onEnable(); } onDisable(): void { super.onDisable(); } bindNode() { this.node.on(Node.EventType.TOUCH_START, (event:EventTouch) => { //目标节点不存在,拦截 if (!this.registNode || this.registNode.isValid == false) { // this.node['_touchListener'].setSwallowTouches(true); event.preventSwallow = true; event.propagationStopped = true; if(this.registNode && this.registNode.isValid == false){ this.close() } return; } //目标区域存在,击中放行 let rect = this.registNode.getComponent(UITransform).getBoundingBoxToWorld(); if ( rect.contains(event.getUILocation()) ) { // this.node['_touchListener'].setSwallowTouches(false); // 命中镂空区域:不拦截,让触摸继续传给底层按钮 event.preventSwallow = true; event.propagationStopped = false; } else { // this.node['_touchListener'].setSwallowTouches(true); // 命中非目标区域:拦截,避免误触底层 event.preventSwallow = true; event.propagationStopped = true; } }, this); //监听事件 this.node.on(Node.EventType.TOUCH_END, (event) => { //目标节点不存在,拦截 if (!this.registNode || this.registNode.isValid == false) { // this.node['_touchListener'].setSwallowTouches(true); event.preventSwallow = true; event.propagationStopped = true; if(this.registNode && this.registNode.isValid == false){ this.close() } return; } //目标区域存在,击中放行 let rect = this.registNode.getComponent(UITransform).getBoundingBoxToWorld(); if ( rect.contains(event.getUILocation()) ) { // this.node['_touchListener'].setSwallowTouches(false); // 命中镂空区域:不拦截,让触摸继续传给底层按钮 event.preventSwallow = true; event.propagationStopped = false; } else { // this.node['_touchListener'].setSwallowTouches(true); // 命中非目标区域:拦截,避免误触底层 event.preventSwallow = true; event.propagationStopped = true; } //目标区域存在,击中放行 if ( rect.contains(event.getUILocation())) { // 先让底层按钮/节点吃到本次 TOUCH_END,再执行引导回调与切换/关闭 this.scheduleOnce(() => { this.registNode = null if (this.finishCallBack) { this.finishCallBack() } if (this.nodeArray.length == 0) { this.close() //EventMgr.emitEvent_custom(ryw_Event.swallowToucheFinishEvent); } else { let nextNode = this.nodeArray.shift() this.nextNode(nextNode) } }, 0); //EventMgr.emitEvent_custom(ryw_Event.swallowToucheEvent); } else { } }, this); } nextNode(registNode){ this.registNode = registNode let str = this.labArray.shift() let posi = MTools.getNodeToTargetPos(this.shouzhiNode, registNode) if(this.isPlayAnim && this.shouzhiNode.active) { // this.shouzhiNode.setPosition(0, 0) tween(this.shouzhiNode) .to(0.6, {x:posi.x, y:posi.y-5}) .call(()=>{ this._focusToNode(registNode) if(str && str != ''){ this.guideNode.active = true this.guideDesc.string = str }else{ this.guideNode.active = false } }) .start() }else{ this.shouzhiNode.setPosition(posi.x+50, posi.y) this._focusToNode(registNode) if(str && str != ''){ this.guideNode.active = true this.guideDesc.string = str }else{ this.guideNode.active = false } } } _ensureHoleBorderGraphics(): Graphics { if (this._holeBorderGraphics && this._holeBorderGraphics.isValid) { return this._holeBorderGraphics } let borderNode = this.maskNode.node.getChildByName('holeBorder') if (!borderNode) { borderNode = new Node('holeBorder') const ut = borderNode.addComponent(UITransform) ut.setAnchorPoint(0.5, 0.5) ut.setContentSize(1, 1) borderNode.setParent(this.maskNode.node) borderNode.setSiblingIndex(0) } this._holeBorderGraphics = borderNode.getComponent(Graphics) || borderNode.addComponent(Graphics) return this._holeBorderGraphics } /** 与引擎 Mask GRAPHICS_RECT 一致的局部坐标原点(考虑锚点) */ _maskLocalRectOrigin(w: number, h: number): { x: number, y: number } { const ap = this.maskNode.node.getComponent(UITransform).anchorPoint return { x: -w * ap.x, y: -h * ap.y } } _redrawHoleStencil(w: number, h: number, radius: number) { const g = this.graphics as Graphics if (!g) { return } const { x, y } = this._maskLocalRectOrigin(w, h) const r = Math.min(radius, Math.min(w, h) * 0.5 - 0.5) g.clear() g.fillColor = new Color(255, 255, 255, 0) g.roundRect(x, y, w, h, Math.max(0, r)) g.fill() } _drawHoleBorderFeather(w: number, h: number, radius: number) { const g = this._ensureHoleBorderGraphics() const { x, y } = this._maskLocalRectOrigin(w, h) const r = Math.min(radius, Math.min(w, h) * 0.5 - 0.5) g.clear() g.lineJoin = Graphics.LineJoin.ROUND g.lineCap = Graphics.LineCap.ROUND const layers = Math.max(1, this.holeFeatherLayers) const maxLw = Math.max(1, this.holeFeatherMaxLine) for (let i = layers; i >= 1; i--) { const t = i / layers const lw = 1 + (maxLw - 1) * t const a = Math.floor(40 * (1 - t * 0.88)) g.lineWidth = lw g.strokeColor = new Color(200, 245, 255, a) g.roundRect(x, y, w, h, Math.max(0, r)) g.stroke() } } _focusToNode(node) { this.scheduleOnce(() => { let rect = node.getComponent(UITransform).getBoundingBoxToWorld(); this.maskNode.node.getComponent(UITransform).setContentSize(0,0) let p = this.node.getComponent(UITransform).convertToNodeSpaceAR(rect.origin); rect.x = p.x; rect.y = p.y; MTools.setNodeToTargetPos(this.maskNode.node, node) const pad = 20 const w = rect.width + pad const h = rect.height + pad this.maskNode.node.getComponent(UITransform).setContentSize(w, h) const rr = Math.min(this.holeCornerRadius, Math.min(w, h) * 0.12) this._redrawHoleStencil(w, h, rr) this._drawHoleBorderFeather(w, h, rr) }) } onInit(): void { // finishCallBack = null // nodeArray: Node[] = []; // labArray: string[] = []; // isPlayAnim = true // blackOpacity = 255 this.maskNode.type = Mask.Type.GRAPHICS_STENCIL this.graphics = this.maskNode.node.getComponent(Graphics) this.nodeArray = this.prema[0] this.labArray = this.prema[1] this.finishCallBack = this.prema[2] this.isPlayAnim = this.prema[3] || true this.blackOpacity = this.prema[4] || 255 if(this.prema[5] === false){ this.hideSpineshouzhi = this.prema[5] }else{ this.hideSpineshouzhi = true } if(!this.hideSpineshouzhi){ this.node.getChildByName('dianji').active = false } this.maskBlackNode.opacity = this.blackOpacity this.bindNode() if (this.nodeArray.length == 0) { this.close() //EventMgr.emitEvent_custom(ryw_Event.swallowToucheFinishEvent); } else { let nextNode = this.nodeArray.shift() this.nextNode(nextNode) } } }