import { _decorator, assetManager, builtinResMgr, Button, Component, error, EventHandler, EventTouch, ImageAsset, instantiate, isValid, IVec2Like, Label, Material, Node, RichText, screen, sp, Sprite, SpriteAtlas, SpriteFrame, Texture2D, Tween, tween, UIOpacity, UITransform, v3, Vec2, Vec3, view, warn, Widget } from "cc"; import StringUtils from "../ExUtils/StringUtils"; import ResLoad from "../ResUrl/ResLoad"; import Language from "../Language"; import { ESounds } from "../MesEvent/ESounds"; import { RichTextTypeData, RichText_Color, RichText_Size, RichText_Outline, RichText_b, RichText_i, RichText_u, IData, SoundType } from "../const/GameInterface"; import { Global } from "../Global"; import InstanceT from "../ExUtils/InstanceT"; import GDUtils from "./GDUtils"; import { EResUrls } from "../ResUrl/EResUrls"; @InstanceT.DecorateInstance() class CNodeUtils { /** 单例 */ public static readonly instance: CNodeUtils; //#region 节点查找 getNodeEx(target: Node | Component, name: string, active: boolean) { let tNode: Node = target instanceof Component ? target.node : target; let arr: Node[] = []; tNode.children.forEach(n => { if (n.name == name && (!active || n.activeInHierarchy)) arr.push(n); arr = arr.concat(this.getNodeEx(n, name, active)); }) return arr; } /**根据节点路径 寻找节点 */ getNode(target: Node | Component, ...url: any[]) { let tNode: Node = target instanceof Component ? target.node : target; for (let i in url) { tNode = tNode.getChildByName(`${url[i]}`); if (!tNode) { ConsoleEx.Error(`查找错误,${url[i]}`) return null; } } return tNode; } //#endregion //#region 节点组件查询 /**获取组件 不存在那么就添加 */ getOrAddComponent(node: Node | Component, cla): any { let temp = node.getComponent(cla); if (!temp) { temp = node.addComponent(cla); } return temp as T; } //#endregion //#region 节点组件属性设置 /**设置节点透明度 */ public setOpacity(node: Node | Component, Opacity: number) { const tClassPoxy: UIOpacity = this.getOrAddComponent(node, UIOpacity); tClassPoxy.opacity = Opacity; } /**设置节点缩放 */ setScale(target: Node | Component, scale: number) { const tNode: Node = target instanceof Component ? target.node : target; tNode.setScale(scale, scale, scale); } /**设置节点显示 */ setActive(target: Node | Component, active: boolean, ADFlag: number = 0) { const tNode: Node = target instanceof Component ? target.node : target; if (tNode.active != active) { tNode.active = active; //ADFlag && active && TDAnalyticsMgr.ins.TrySendtrack(GEData.GEeventName.ad_show, { ad_id: ADFlag }); } } /**设置按钮是否禁用 */ setBtnInteractable(btn: Button, _interactable: boolean) { btn.interactable = _interactable; const tempSprite = btn.getComponent(Sprite) if (tempSprite) { tempSprite.grayscale = !_interactable; } } /**缓动图片范围 */ TweenSpriteRange(node: Node | Sprite, fillRange: number, time: number = 0.2) { const tSprite: Sprite = node instanceof Node ? node.getComponent(Sprite) : node; if (time > 0) { tween(tSprite).to(time, { fillRange: fillRange }).start(); } else { tSprite.fillRange = fillRange; } } /**设置数组节点 全部是否置灰 */ public setAllGray(node: Node[], isGray: boolean, exclude: Node[] = []) { node.forEach(child => { this.setChiladAllGray(child, isGray, exclude); }) } /**设置节点以及子节点 全部是否置灰 */ public setChiladAllGray(node: Node | Sprite, isGray: boolean, exclude: Node[] = []) { const tNode: Node = node instanceof Sprite ? node.node : node; const children = tNode.children; children.forEach(child => { if (!exclude.includes(child)) { this.setChiladAllGray(child, isGray, exclude); } }) if (!exclude.includes(tNode)) { this.setGray(tNode, isGray); } } /**设置节点是否置灰 */ public setGray(node: Node | Sprite, isGray: boolean) { const tSprite: Sprite = node instanceof Node ? node.getComponent(Sprite) : node; if (tSprite) { tSprite.grayscale = isGray; } else { const label = node.getComponent(Label); if (label) { if (!label["Org"]) { label["Org"] = label.material; } if (isGray) { label.material = builtinResMgr.get('ui-sprite-gray-material') } else { label.material = label["Org"]; } } } } /** * 更新label文字 * **/ public switchText(node: Node | Label, str: any, forceUpdate = false, isNum = false) { if (!isValid(node)) { warn('要更新的节点为空,str=' + str); return; } const label: Label = node instanceof Node ? node.getComponent(Label) : node; if (!label) { return; } label.node.attr({ original: str }); label.string = isNum ? str : Language.getValue(str); // if (label.useSystemFont) { // label.font = AppLanguage.getFont() // } forceUpdate && label.updateRenderData(); } /** * 更新RichText文字 * **/ public switchRichText(node: Node | RichText, str: any, forceUpdate = false, isNum = false) { if (!isValid(node)) { warn('要更新的节点为空,str=' + str); return; } const label: RichText = node instanceof Node ? node.getComponent(RichText) : node; if (!label) { return; } label.node.attr({ original: str }); label.string = isNum ? str : Language.getValue(str); if (label.useSystemFont) { //@ts-ignore //label.font = AppLanguage.getFont() } forceUpdate && label["_updateRichTextStatus"](); } /** * 更新label文字,文字颜色与品质相关 * **/ public switchTextByQuality(node: Node | Label, str: any, quality: number, forceUpdate = false, isNum = false) { if (!isValid(node)) { warn('要更新的节点为空,str=' + str); return; } const label: Label = node instanceof Node ? node.getComponent(Label) : node; if (!label) { return; } label.color = label.color.fromHEX(GDUtils.GetQualityColorHEX(quality)); label.node.attr({ original: str }); label.string = isNum ? str : Language.getValue(str); forceUpdate && label.updateRenderData(); } // /** // * 更新label文字,文字颜色与品质相关 // * **/ // public switchRichTextByQuality(node: Node | RichText, str: any, quality: number, forceUpdate = false, isNum = false) { // if (!isValid(node)) { // warn('要更新的节点为空,str=' + str); // return; // } // const label: RichText = node instanceof Node ? node.getComponent(RichText) : node; // if (!label) { return; } // label.node.attr({ original: str }); // label.string = isNum ? str : Language.getValue(str); // if (label.useSystemFont) { // //@ts-ignore // //label.font = AppLanguage.getFont() // } // forceUpdate && label["_updateRichTextStatus"](); // } /**数字文本缓动 */ TweenNumText(target: Node | Label, toNum: number, isConvert: boolean = true) { if (!isValid(target)) { warn('要更新的节点为空'); return; } const tNode: Node = target instanceof Node ? target : target.node; Tween.stopAllByTarget(tNode); if (tNode["CurNum"] != 0 && !tNode["CurNum"]) { tNode["TargetNum"] = toNum; tNode["CurNum"] = toNum; } tNode["CurNum"] = tNode["TargetNum"]; tNode["TargetNum"] = toNum; let offsetNum = tNode["TargetNum"] - tNode["CurNum"]; if (offsetNum != 0) { tween(tNode).update(0.3, (node: Node, ratio: number) => { let tNum = tNode["CurNum"] + offsetNum * ratio; this.switchText(node, isConvert ? StringUtils.HurtConvert(Math.floor(tNum)) : Math.floor(tNum)); }).start(); } else { this.switchText(target, isConvert ? StringUtils.HurtConvert(Math.floor(tNode["CurNum"])) : Math.floor(tNode["CurNum"])); } } addBtnLister(node: Node, callback: Function, target?: any, audio: SoundType = ESounds.So_click) { node.off('click'); node.on('click', () => { if (audio == ESounds.So_click) { Global.Audio.playEffect(ESounds.So_click); } else { Global.Audio.playEffect(audio); } callback.call(target, node) }) } addEventHandler(node: Node, calssname = '', callFunc = '', customData = '') { let event_handler = new Component.EventHandler(); event_handler.target = node; event_handler.component = calssname; event_handler.handler = callFunc; event_handler.customEventData = customData; return event_handler; } /** * 添加button事件 * @param node * @param callback * @param target * @param isAudio */ addBtnComponent(node: Node, callback: Function, target?: any, isAudio: SoundType = ESounds.So_click) { if (!node.getComponent(Button)) { let btn: Button = node.addComponent(Button); btn.transition = Button.Transition.SCALE; btn.duration = 0.1; btn.target = node; btn.zoomScale = 0.93; } this.addBtnLister(node, callback, target, isAudio); } addNoScaleBtnComponent(node: Node, callback: Function, target?: any, isAudio: SoundType = ESounds.So_click) { if (!node.getComponent(Button)) { let btn: Button = node.addComponent(Button); btn.transition = Button.Transition.NONE; btn.duration = 0.1; btn.target = node; btn.zoomScale = 0.93; } this.addBtnLister(node, callback, target, isAudio); } ClearBtnCommontEffect(node: Node) { node.off('click'); } /**添加按钮长按 */ addBtnLongpress(node: Node | Button, downCallBack: Function, upCallback: Function) { let tNode = null; if (node instanceof Node) { tNode = node; } else { tNode = node.node; } tNode.off(Node.EventType.TOUCH_START); tNode.off(Node.EventType.TOUCH_MOVE); tNode.off(Node.EventType.TOUCH_END); tNode.off(Node.EventType.TOUCH_CANCEL); let isDown = false; tNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => { isDown = true; downCallBack && downCallBack(event); }, this) tNode.on(Node.EventType.TOUCH_END, (event: EventTouch) => { isDown = false; upCallback && upCallback(event); }, this) tNode.on(Node.EventType.TOUCH_CANCEL, (event: EventTouch) => { isDown = false; upCallback && upCallback(event); }, this) } /** * * @param node * @param callback * @param target */ addMoveComponent(node: Node, callback: Function, target?: any, TiggerFuntion?: Function) { node.off(Node.EventType.TOUCH_START); node.off(Node.EventType.TOUCH_MOVE); node.off(Node.EventType.TOUCH_END); let isMove = false; node.on(Node.EventType.TOUCH_START, (event: EventTouch) => { isMove = false; TiggerFuntion && TiggerFuntion(true); }, this) node.on(Node.EventType.TOUCH_MOVE, (event) => { let touch = event.touch; let deltaMove = touch.getLocation().sub(touch.getStartLocation()); if (deltaMove.mag() > 10) { isMove = true; } }, this) node.on(Node.EventType.TOUCH_END, (event) => { TiggerFuntion && TiggerFuntion(false); !isMove && callback.call(target, node, event); }, this) } /**富文本处理 列表*/ ContainRichtTextByList(value: string, list: RichTextTypeData[]): string { let tVa: string = value; for (let i = 0; i < list.length; i++) { const tData = list[i]; tVa = this.ContainRichtText(tVa, tData); } return tVa; } /**富文本处理 */ ContainRichtText(value: string, data: RichTextTypeData): string { if (data instanceof RichText_Color) { return `${value}`; } else if (data instanceof RichText_Size) { return `${value}` } else if (data instanceof RichText_Outline) { return `${value}` } else if (data instanceof RichText_b) { return `${value}`; } else if (data instanceof RichText_i) { return `${value}c`; } else if (data instanceof RichText_u) { return `${value}`; } } /**添加富文本点击事件 */ AddRichTextClick(tagetNode: Node | RichText, CallBack: Function, functionName?: string) { let tRichText: RichText = null; if (tagetNode instanceof RichText) { tRichText = tagetNode; } else { tRichText = tagetNode.getComponent(RichText) } if (!tRichText) { error("添加富文本点击失败 该节点没有富文本组件"); return; } functionName = functionName ? functionName : CallBack.name; functionName = functionName.replace("bound", ""); functionName = functionName.replace(" ", ""); tRichText[functionName] = CallBack; } SwitchSpine(node: Node | sp.Skeleton, file: string, bundleName: string, animation: string | string[] = 'idle', isLoop: boolean = true, skin: string = 'default', back: Function = null) { const spine: sp.Skeleton = node instanceof Node ? node.getComponent(sp.Skeleton) : node; // const url = EResUrls.Spine(file); ResLoad.Ins.Spine(file, bundleName, (err, skeletonData: sp.SkeletonData) => { if (!isValid(spine)) { warn('spine 被移除') return; } if (err || !skeletonData) { error('加载资源出错: ' + file); return; } spine.skeletonData = skeletonData; if (Array.isArray(animation)) { spine.setAnimation(0, animation[0], animation.length == 1); animation.length > 1 && spine.addAnimation(0, animation[1], isLoop); } else { spine.setAnimation(0, animation, isLoop); } skin && spine.setSkin(skin); back && back(); }) } SwitchTexture(file: string, node: Node | Sprite, bundleName: string, width: number = 0, back: Function = null) { const spr: Sprite = node instanceof Node ? node.getComponent(Sprite) : node; if (!bundleName) { ConsoleEx.Error("路径错误",) } ResLoad.Ins.Sprite(file, bundleName, (err, spriteFrame: SpriteFrame) => { if (!isValid(spr)) { warn('Sprite 被移除') return; } if (err || !spriteFrame) { error('加载资源出错: ' + file); return; } if (spr["MY_ADD"] && spr.spriteFrame) { spr.spriteFrame.decRef() } spr.spriteFrame = spriteFrame; if (!spr.spriteFrame) { error('加载资源出错: ' + file); } spr.spriteFrame.addRef(); spr["MY_ADD"] = 1; width && this.scaleNode(spr.node, width); back && back(); }) } SwitchTextureByAutoAtlas(file: string, atlas: string, node: Node | Sprite, bundleName: string, width: number = 0, back: Function = null) { const spr: Sprite = node instanceof Node ? node.getComponent(Sprite) : node; ResLoad.Ins.AutoAtlas(atlas, bundleName, (err, spriteFrame) => { if (!isValid(spr)) { warn('Sprite 被移除') return; } if (err) { error('加载资源出错: ' + atlas); return; } file = file.replace("Texture/", ""); spr.spriteFrame = spriteFrame.getSpriteFrame(file); width && this.scaleNode(spr.node, width); back && back(); }) } SwitchTextureByData(node: Node | Sprite, data: IData, width: number = 0, back: Function = null) { const url = GDUtils.getUrl(data); const bundle = GDUtils.getUBundle(data.type, data.id); const Atlas = GDUtils.getAtlas(data.type); if (Atlas) { this.SwitchTextureByAutoAtlas(url, Atlas, node, bundle, width, back) } else { this.SwitchTexture(url, node, bundle, width, back); } } /**加载远程图片 */ SwitchRomateTexture(file: string, node: Node | Sprite, width: number = 0, back: Function = null) { if (!file.includes("http")) { back && back(); ConsoleEx.Error("请检查链接:", file) return; } let isHaveExt: boolean = file.includes(".png") || file.includes(".jpg"); const spr: Sprite = node instanceof Node ? node.getComponent(Sprite) : node; let lamuda = (err, res: ImageAsset) => { if (!isValid(spr)) { warn('Sprite 被移除') return; } if (err) { error('加载资源出错: ' + file); return; } const spriteFrame = new SpriteFrame(); const texture = new Texture2D(); texture.image = res; spriteFrame.texture = texture; spr.spriteFrame = spriteFrame; width && this.scaleNode(spr.node, width); back && back(); } if (isHaveExt) { assetManager.loadRemote(file, lamuda) } else { assetManager.loadRemote(file, { ext: '.png' }, lamuda) } } scaleNode(target: Node | Component, width: number, height = 0) { const tNode = target instanceof Node ? target : target.node; if (isValid(tNode)) { if (!height) { height = width; } const wScale = width / tNode.getComponent(UITransform).width; const hScale = height / tNode.getComponent(UITransform).height; const sc = Math.min(wScale, hScale); tNode.setScale(v3(sc, sc, sc)) } } scaleNodeW(target: Node | Component, width: number) { const tNode = target instanceof Node ? target : target.node; if (isValid(tNode)) { const wScale = width / tNode.getComponent(UITransform).width; tNode.setScale(v3(wScale, wScale, wScale)) } } scaleNodeH(target: Node | Component, height) { const tNode = target instanceof Node ? target : target.node; if (isValid(tNode)) { const wScale = height / tNode.getComponent(UITransform).height; tNode.setScale(v3(wScale, wScale, wScale)) } } /** * 二阶贝塞尔曲线 运动 * @param target * @param {number} duration * @param {} c1 起点坐标 * @param {} c2 控制点 * @param {Vec3} to 终点坐标 * @param opts * @returns {any} */ bezierTo(target: Node, duration: number, c1: Vec2, c2: Vec2, to: Vec3, opts: any, Callback?: Function) { opts = opts || Object.create(null); /** * @desc 二阶贝塞尔 * @param {number} t 当前百分比 * @param {} p1 起点坐标 * @param {} cp 控制点 * @param {} p2 终点坐标 * @returns {any} */ let twoBezier = (t: number, p1: Vec2, cp: Vec2, p2: Vec3) => { let x = (1 - t) * (1 - t) * p1.x + 2 * t * (1 - t) * cp.x + t * t * p2.x; let y = (1 - t) * (1 - t) * p1.y + 2 * t * (1 - t) * cp.y + t * t * p2.y; return v3(x, y, 0); }; opts.onUpdate = (arg: Vec3, ratio: number) => { target.setPosition(twoBezier(ratio, c1, c2, to)) Callback && Callback(); }; return tween(target).to(duration, {}, opts); } /** * 添加button事件 * @param node * @param callback * @param target * @param isAudio */ addNodeTounch(node: Node, onMouseUp: Function, target?: any, onMouseEnter?: Function, onMouseMove?: Function, onMouseLeave?: Function, OnMoveDir?: Function) { this.clearNodeTounch(node); let isMove: boolean = false; node.on(Node.EventType.TOUCH_START, (event: EventTouch) => { onMouseEnter && onMouseEnter(event); }, target); node.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { let tempX = event.getStartLocation().x - event.getLocationX(); let tempY = event.getStartLocation().y - event.getLocationY(); let dirX = Math.abs(tempX); let dirY = Math.abs(tempY); if (dirX > 14 || dirY > 14) { isMove = true; OnMoveDir && OnMoveDir(event, tempX, tempY); } onMouseMove && onMouseMove(event); }, target); node.on(Node.EventType.TOUCH_END, (event) => { onMouseUp && !isMove && onMouseUp(event); isMove = false; onMouseLeave && onMouseLeave(event); }, target); onMouseLeave && node.on(Node.EventType.TOUCH_CANCEL, onMouseLeave, target); } clearNodeTounch(node: Node) { node.off(Node.EventType.TOUCH_START); node.off(Node.EventType.TOUCH_MOVE); node.off(Node.EventType.TOUCH_END); node.off(Node.EventType.TOUCH_CANCEL); } //#endregion setADWaitShow(btn_Reward: Node | Component) { if (!window["tt"]) { // this.SetNodeActive(btn_Reward, true) // setTimeout(() => { // if (isValid(btn_Reward)) { // this.SetNodeActive(btn_Reward, true) // } // }, 1000) } } GetPosRange(pos: Vec3, org: Node, Traget: Node, offset: IVec2Like) { let tNode: Node = Traget instanceof Component ? Traget.node : Traget; let tY = pos.y - offset.y - org.getComponent(UITransform).height / 2; if (tY < (-tNode.getComponent(UITransform).height / 2)) { tY = pos.y + offset.y; } else { tY = pos.y - offset.y; } let tX = pos.x; tX = Math.max(-(tNode.getComponent(UITransform).width / 2 - org.getComponent(UITransform).width / 2) + 20, tX); tX = Math.min(tNode.getComponent(UITransform).width / 2 - org.getComponent(UITransform).width / 2 - 20, tX); return new Vec3(tX, tY, 0) } GetNodeInScreenRect(node: Node): { left: number, top: number, width: number, height: number } { const rect = node.getComponent(UITransform)!.getBoundingBoxToWorld(); const dpi = screen.devicePixelRatio; const scaleX = view.getScaleX(); const scaleY = view.getScaleY(); const left = (rect.x * scaleX) / dpi; const top = (screen.windowSize.height - (rect.y + rect.height) * scaleY) / dpi; const width = (rect.width * scaleX) / dpi; const height = (rect.height * scaleY) / dpi; return { left, top, width, height }; } } export default CNodeUtils.instance; window["CNodeUtils"] = CNodeUtils.instance;