/** @format */ import {ConstValue} from '../data/ConstValue' class CCUtils { /** * 设置Label文本 * @param str * @param node * @param childUrl */ public setLabel(str: string, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { const child = cc.find(childUrl, node) if (child) { node = cc.find(childUrl, node) } } let label = node.getComponent(cc.Label) if (node.getComponent('i18nLabel')) { label = node.getComponent('i18nLabel') } if (label) { label.string = str } else { cc.warn('setLabel err, str:', str) } } public labelForceUpdateRenderData(node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { const child = cc.find(childUrl, node) if (child) { node = cc.find(childUrl, node) } } let label = node.getComponent(cc.Label) if (label) { ;(label)._forceUpdateRenderData() } else { cc.warn('labelForceUpdateRenderData err') } } /** * 设置LabelOutLine颜色 * @param color '#XXXXXX'| cc.Color * @param node * @param childUrl */ public setLabelOutlineColor(color: string | cc.Color, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const label = node.getComponent(cc.LabelOutline) if (label) { if (typeof color == 'string') { label.color = new cc.Color().fromHEX(color) } else { label.color = color } } else { cc.warn('setLabelOutlineColor err, color:', color) } } /** * 设置RichLabel文本 * @param str * @param node * @param childUrl */ public setRichLabel(str: string, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } let label = node.getComponent(cc.RichText) if (node.getComponent('i18nLabel')) { label = node.getComponent('i18nLabel') } if (label) { label.string = str } else { cc.warn('setRichLabel err, str:', str) } } public setRichLabelGray(node: cc.Node, childUrl?: string, grayColor?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } let label = node.getComponent(cc.RichText) let gray = grayColor ? grayColor : '#AAAAAA' if (label) { label.string = `${label.string.replace(/]*>/g, ``)}` } else { cc.warn('setRichLabelGray err, label:', label) } } /** * 按照spriteFrame设置大小 */ public setSFAndScale(spriteFrame: cc.SpriteFrame, scale: number = 1, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } node.getComponent(cc.Sprite).spriteFrame = spriteFrame if (spriteFrame) { node.width = spriteFrame.getRect().width * scale node.height = spriteFrame.getRect().height * scale } else { cc.warn('setSFAndScale err') } } /** * 设置spriteFrame */ public setSpriteFrame(spriteFrame: cc.SpriteFrame, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } let sprite = node.getComponent(cc.Sprite) if (!sprite) { cc.warn('setSFAndScale err no sprite') return } sprite.spriteFrame = spriteFrame } /** * * @param isInteract * @param node * @param childUrl */ public setBtnInteract(isInteract: boolean, node: cc.Node, childUrl?: string, isHasWarn: boolean = true) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const button = node.getComponent(cc.Button) if (button) { button.interactable = isInteract } else { if (isHasWarn) { cc.warn('setBtnInteract err') } } } /** * 进度条 设置进度 * @param progress * @param node * @param childUrl */ public setProgress(progress: number, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const progressBar = node.getComponent(cc.ProgressBar) if (progressBar) { progressBar.progress = progress } else { cc.warn('setProgress err') } } /** * 滑动条 设置进度 * @param progress * @param node * @param childUrl */ public setSliderProgress(progress: number, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const progressBar = node.getComponent(cc.Slider) if (progressBar) { progressBar.progress = progress } else { cc.warn('setSiderProgress err') } } /** * * @param isInteract * @param node(父节点) * @param childUrl */ public setTogglesInteract(isInteract: boolean, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const toggle = node.getComponent(cc.Toggle) if (toggle) { toggle.enabled = isInteract } } /** * * @param checkedIndex * @param node(父节点) * @param childUrl */ public setTogglesChecked(checkedIndex: number, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const children = node.children children.forEach((value, index) => { const toggle = value.getComponent(cc.Toggle) if (toggle) { toggle.isChecked = index == checkedIndex } }) } public setColor(color: string, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } node.color = cc.Color.WHITE.fromHEX(color) } /** * 按钮延迟激活 */ public btnDelayActive(time: number, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const btn = node.getComponent(cc.Button) if (btn) { btn.interactable = false node.stopAllActions() node.runAction( cc.sequence( cc.delayTime(time), cc.callFunc(() => { btn.interactable = true }), ), ) } else { cc.warn('btnDelayActive err') } } /** * 设置EditBox文本 * @param str * @param node * @param childUrl */ public setEditBox(str: string, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { const child = cc.find(childUrl, node) if (child) { node = cc.find(childUrl, node) } } const editBox = node.getComponent(cc.EditBox) if (editBox) { editBox.string = str } else { cc.warn('setEditBox err, str:', str) } } /** * 停止动画 */ public stopAni(node: cc.Node, clipName?: string, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const ani = node.getComponent(cc.Animation) if (ani) { ani.play(clipName) ani.setCurrentTime(0, clipName) ani.sample(clipName) ani.stop(clipName) } else { //cc.warn('stopAni err') } } /** * 开始动画 */ public playAni(aniStr: string, startTime: number = 0, node: cc.Node, childUrl?: string): cc.Animation { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } this.stopAni(node, aniStr) const ani = node.getComponent(cc.Animation) if (ani) { ani.play(aniStr, startTime) return ani } else { cc.warn('playAni err') } } public playNodeAllAni(node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } node.active = true const children = node.children children.forEach(value => { const aniCom = value.getComponent(cc.Animation) if (aniCom) { this.stopAni(value) aniCom.play() } }) } public stopNodeAllAni(node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } node.active = false const children = node.children children.forEach(value => { const aniCom = value.getComponent(cc.Animation) if (aniCom) { this.stopAni(value) } }) } /** * 判断当前节点是否在屏幕内 */ public isInScreen(node: cc.Node, yIn: boolean = false, xIn: boolean = false) { if (!cc.isValid(node)) { return } let isyIn = false let isxIn = false const worldPos = node.convertToWorldSpaceAR(cc.v2(0, 0)) isyIn = worldPos.y - node.height <= 1136 && worldPos.y + node.height >= 0 isxIn = worldPos.x - node.width <= 640 && worldPos.x + node.width >= 0 if (yIn) { return isyIn } if (xIn) { return isxIn } return isxIn && isyIn } /** * 获取全部的子节点(递归) * @param node */ public getAllChildrenByRecurve(node: cc.Node) { let ret: cc.Node[] = [] ret = ret.concat(node.children) for (let i = 0; i < node.children.length; i++) { ret = ret.concat(this.getAllChildrenByRecurve(node.children[i])) } return ret } /** * 获取全部的子节点(广度) * @param node */ public getAllChildrenBFS(node: cc.Node) { if (!node || !node.children.length) { return } const ret = [] let stack = [] // 先将第一层节点放入栈 for (let i = 0, len = node.children.length; i < len; i++) { stack.push(node.children[i]) } let item = null while (stack.length) { item = stack.shift() ret.push(item) // 如果该节点有子节点,继续添加进入栈底 if (item.children && item.children.length) { stack = stack.concat(item.children) } } return ret } /** * 获取全部的子节点(深度) * @param node */ public getAllChildrenDFS(node: cc.Node): cc.Node[] { if (!node || !node.children.length) { return } const ret = [] let stack = [] // 先将第一层节点放入栈 for (let i = 0, len = node.children.length; i < len; i++) { stack.push(node.children[i]) } let item = null while (stack.length) { item = stack.shift() ret.push(item) // 如果该节点有子节点,继续添加进入栈顶 if (item.children && item.children.length) { stack = item.children.concat(stack) } } return ret } /** * 获取name应该对应的node * @param {节点} nodes * @param {*} val */ public findNodeByName(node: cc.Node, name: string): cc.Node | null { if (node.name === name) { return node } for (let i = 0; i < node.children.length; i++) { if (this.findNodeByName(node.children[i], name)) { // 若找到则返回该节点 return this.findNodeByName(node.children[i], name) } } return null } public setNodeColorGray(isGray: boolean, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } node.color = isGray ? cc.Color.GRAY : cc.Color.WHITE } /** * 将节点置灰 * @param node * @param isGray */ public setAllGray(isGray: boolean, node: cc.Node, childUrl?: string) { if (childUrl) { node = cc.find(childUrl, node) } const all = this.getAllChildrenDFS(node).concat(node) if (all) { for (let i = 0; i < all.length; i++) { if (all[i].name.indexOf('notGray') != -1) { continue } this.setBtnInteract(!isGray, all[i], null, false) this.setSpriteGray(isGray, all[i]) //文字相关置灰 const customKey = 'custom_originColor' let labGray = (obj: cc.LabelOutline | cc.Label) => { if (!obj?.isValid) return if (!isGray) { let originColor: string = obj[customKey] if (originColor) { if (obj instanceof cc.Label) { obj.node.color = cc.Color.WHITE.fromHEX(originColor) } else { obj.color = cc.Color.WHITE.fromHEX(originColor) } obj[customKey] = null } } else { let lbColor = obj instanceof cc.Label ? obj.node.color : obj.color if (!obj[customKey]) obj[customKey] = lbColor.toHEX() if (obj instanceof cc.Label) { obj.node.color = cc.Color.WHITE.fromHEX('#272727') } else { obj.color = cc.Color.WHITE.fromHEX('#A79D9C') } } } let label = all[i].getComponent(cc.Label) labGray(label) let labOutline = all[i].getComponent(cc.LabelOutline) labGray(labOutline) } } } /** * 将精灵置灰 * @param node * @param isGray */ public setSpriteGray(isGray: boolean, node: cc.Node, childUrl?: string) { if (!cc.isValid(node)) { return } if (childUrl) { node = cc.find(childUrl, node) } const sprite = node.getComponent(cc.Sprite) if (!sprite) { return } if (isGray) { sprite.setMaterial(0, cc.Material.getBuiltinMaterial('2d-gray-sprite')) } else { sprite.setMaterial(0, cc.Material.getBuiltinMaterial('2d-sprite')) } } /** * 返回使用数学计量单位K(千) M(兆) G(吉)....来表示的数字 * @param n * @param fix 保留几位小数 0 */ // public getSCI(n: number | string | Long, fix?:number): string { // const base = Long.fromValue(n); // let baseString = n.toString(); // 小数部分 // if (base.lessThanOrEqual(10000)) { // return base.toString(); // } // const si = [ // { value: 1, symbol: '' }, // { value: 1E3, symbol: 'K' }, // { value: 1E6, symbol: 'M' }, // { value: 1E9, symbol: 'G' }, // { value: 1E12, symbol: 'T' }, // { value: 1E15, symbol: 'P' }, // { value: 1E18, symbol: 'E' }, // ]; // if (fix && baseString.length > fix) { // if (baseString.length > 0) { // baseString = `.${baseString.substr(1, fix)}`; // } else { // baseString = ''; // } // } else { // baseString = ''; // } // let i; // for (i = si.length - 1; i > 0; i--) { // if (base.greaterThanOrEqual(si[i].value)) { // break; // } // } // return (base.divide(si[i].value)).toString() + baseString + si[i].symbol; // } /** * 将一个节点转换为Canvas下的坐标AR * @param node 指定的节点 */ public convertToWorldSpaceCanvasAR(node: cc.Node) { const pos = node.parent.convertToWorldSpaceAR(node.position) return cc.Canvas.instance.node.convertToNodeSpaceAR(pos) } /** * 将Canvas下的坐标转换为一个节点AR * @param node 指定的节点 */ public convertCanvasToNodeAR(node: cc.Node, pos: cc.Vec2 = cc.Vec2.ZERO) { const world = cc.Canvas.instance.node.convertToWorldSpaceAR(pos) return node.convertToNodeSpaceAR(world) } /** * node节点在target节点下的坐标AR * @param node 指定的节点 */ public convertNode2NodePosAR(node: cc.Node, target: cc.Node) { const pos = this.convertToWorldSpacePos(node) return this.convertWorldPosToNode(target, pos) } // 将一个节点转换为世界坐标 public convertToWorldSpacePos(node: cc.Node) { return node.parent.convertToWorldSpaceAR(node.getPosition()) } // 将世界坐标转为节点内坐标 public convertWorldPosToNode(node: cc.Node, pos: cc.Vec2) { return node.convertToNodeSpaceAR(pos) } // 将触点坐标转为世界坐标 public convertTouchPosToWorld(e) { let screenPos = e.getLocation() let dSize = cc.view.getDesignResolutionSize() let dV2 = cc.v2(dSize.width, dSize.height) let winV2 = cc.v2(cc.winSize.width, cc.winSize.height) return screenPos.sub(winV2.sub(dV2).divide(2)) } // 角度转弧度 angle2radian(angle: number): number { // 角度转弧度公式 // π / 180 * 角度 // 计算出弧度 let radian = (Math.PI / 180) * angle // 返回弧度 return radian } // 弧度转角度 radian2angle(radian: number): number { // 弧度转角度公式 // 180 / π * 弧度 // 计算出角度 let angle = (180 / Math.PI) * radian // 返回弧度 return angle } // 角度转向量 angle2pos(angle: number): cc.Vec2 { // tan = sin / cos // 将传入的角度转为弧度 let radian = this.angle2radian(angle) // 算出cos,sin和tan let cos = Math.cos(radian) // 邻边 / 斜边 let sin = Math.sin(radian) // 对边 / 斜边 let tan = sin / cos // 对边 / 邻边 // 结合在一起并归一化 let vec = cc.v2(cos, sin).normalize() // 返回向量 return vec } // 向量转角度 pos2angle(vector: cc.Vec2, curNormalize: cc.Vec2 = cc.v2(1, 0)): number { // 将传入的向量归一化 let dir = vector.normalize() // 计算出目标角度的弧度 let radian = dir.signAngle(curNormalize) // 把弧度计算成角度 let angle = -this.radian2angle(radian) // 返回角度 return angle } /** * 震屏效果 * @param duration 震屏时间 */ public shakeEffect( duration: number, node: cc.Node, origin: cc.Vec2, callBack?: Function, bgNode?: cc.Node, isShakeX = false, random: number = 5, ) { node.setPosition(origin) const speed = 0.02 const action = [] let num = duration / speed if (num > 10) { num = 10 } let originScale = 1 if (bgNode) { originScale = bgNode.scale bgNode.scale = originScale * (1 + 0.1) } for (let i = 0; i < num; i++) { let randomX = 0 if (isShakeX) { randomX = Math.randomRangeInt(-random, random) } const randomY = Math.randomRangeInt(-random, random) action.push(cc.moveTo(speed, origin.sub(cc.v2(randomX * node.scale, randomY * node.scale)))) } const shake = node.runAction(cc.repeatForever(cc.sequence(action))) setTimeout(() => { if (cc.isValid(node)) { node.stopAction(shake) node.setPosition(origin) if (bgNode) { bgNode.scale = originScale } callBack && callBack() } }, duration * 1000) } public capture() { // REAL_WIDTH REAL_HEIGHT 可能为小数向下取整避免有作为下标访问数组会数据错位 const width = Math.floor(ConstValue.REAL_HEIGHT) const height = Math.floor(ConstValue.REAL_WIDTH) const node = new cc.Node() node.parent = cc.director.getScene() node.setPosition(width / 2, height / 2) const camera = node.addComponent(cc.Camera) const texture = new cc.RenderTexture() const gl = cc.game['_renderContext'] texture.initWithSize(width, height, gl.STENCIL_INDEX8) camera.targetTexture = texture camera.render(cc.Canvas.instance.node) camera.enabled = false const data = texture.readPixels() const picData = new Uint8Array(width * height * 4) const rowBytes = width * 4 for (let row = 0; row < height; row++) { const srow = height - 1 - row const start = srow * width * 4 const reStart = row * width * 4 for (let i = 0; i < rowBytes; i++) { picData[reStart + i] = data[start + i] } } camera.targetTexture = null node.destroy() texture.initWithData(picData, cc.Texture2D.PixelFormat.RGBA8888, width, height) const spriteFrame = new cc.SpriteFrame() spriteFrame.setTexture(texture) return spriteFrame } public getBundleAsync(name) { return new Promise((resolve: (bundle: cc.AssetManager.Bundle) => void, reject) => { let callFuc = (err, bundle) => { if (err) { console.error('loadBundle error name:', name) reject(null) } else { resolve(bundle) } } cc.assetManager.loadBundle(name, callFuc) }) } /** * * 返回节点在世界坐标系下的对齐轴向的包围盒(AABB)不包含子节点的世界边框 * * @param {节点} node节点 */ getBoundingBoxToWorld(node: any): cc.Rect { let w_n: number = node._contentSize.width let h_n: number = node._contentSize.height let rect_o = cc.rect(-node._anchorPoint.x * w_n, -node._anchorPoint.y * h_n, w_n, h_n) node._calculWorldMatrix() rect_o.transformMat4(rect_o, node._worldMatrix) return rect_o } /** * * 节点是否包含子节点 * * @param {节点1} node1 容器判断节点 * @param {节点2} node2 拖动节点 */ contains(node1: cc.Node, node2: cc.Node) { // 判断node2的中心点0,0是否在node1内 return this.getBoundingBoxToWorld(node1).contains(node2.convertToWorldSpaceAR(cc.v2(0, 0))) } /** * * 节点是否包含子节点 * * @param {节点1} node1 * @param {世界坐标} p */ containsP(node: cc.Node, p: cc.Vec3) { return this.getBoundingBoxToWorld(node).contains(cc.v2(p.x, p.y)) } /** * 获取pos应该对应的node * @param {节点} nodes * @param {世界坐标} position */ findNodeByPosition(nodes, position) { for (let iNode of nodes) { if (this.containsP(iNode, position) && iNode.active) { return iNode } } return null } setComponentByObj(component, obj) { for (let key in obj) { component[key] = obj[key] } } isEditorScene() { return cc.director.getScene().name == 'Editor' } drawLine(graphics, touches) { // 最小的移动距离 const MIN_POINT_DISTANCE = 1 // 从0开始移动1 graphics.moveTo(touches[0].x, touches[0].y) // 从1开始移动n let lastIndex = 0 for (let i = 1, l = touches.length; i < l; i++) { // 向量减法,并返回新结果 if (touches[i].sub(touches[lastIndex]).mag() < MIN_POINT_DISTANCE) { continue } lastIndex = i // 画线 graphics.lineTo(touches[i].x, touches[i].y) } graphics.stroke() } addBtnClickEvent(btnNode, fucName, target, componentName) { if (btnNode) { let button = btnNode.getComponent(cc.Button) if (button && button.clickEvents.length == 0) { let clickEventHandler = new cc.Component.EventHandler() clickEventHandler.target = target clickEventHandler.component = componentName clickEventHandler.handler = fucName button.clickEvents.push(clickEventHandler) } } } instantChildren(origin, num, parent?): cc.Node[] { let nodes = [] if (!parent) parent = origin.parent for (let i = 0; i < num; i++) { let node = parent.children[i] if (!node) { node = cc.instantiate(origin) node.parent = parent } node.active = true nodes.push(node) } for (let i = num; i < parent.children.length; i++) { let node = parent.children[i] if (node) { node.active = false } } return nodes } getCirclePoints(r, pos, count, randomScope) { let points = [] let radians = (Math.PI / 180) * Math.round(360 / count) for (let i = 0; i < count; i++) { let x = pos.x + r * Math.sin(radians * i) let y = pos.y + r * Math.cos(radians * i) points.unshift(cc.v3(x + (Math.random() - 0.5) * randomScope, y + (Math.random() - 0.5) * randomScope, 0)) } return points } circleFlyNodes( nodes: cc.Node[], parent: cc.Node, startPos: cc.Vec3, toPos: cc.Vec3, nodeFinishCb: Function, finishCb: Function, ) { let circlePoints = this.getCirclePoints(60, startPos, nodes.length, 40) circlePoints.forEach((value, index) => { let tmp = nodes[index] tmp.parent = parent tmp.zIndex = 9999 tmp.active = true tmp.position = startPos tmp.scale = 0.5 let bezier = [] for (let i = 0; i < 3; i++) { if (i == 2) { bezier.push(toPos) } else { bezier.push(cc.v2(Math.randomRangeInt(-400, 400), Math.randomRangeInt(-200, 200))) } } let action = cc.sequence( cc.moveTo(0.6, value), cc.delayTime(index * 0.03), cc.bezierTo(1, bezier).easing(cc.easeIn(3.0)), cc.callFunc(() => { if (cc.isValid(tmp)) { tmp.parent = null if (nodeFinishCb) { nodeFinishCb(tmp) } else { tmp.destroy() } } if (index == circlePoints.length - 1) { finishCb && finishCb() } }), ) cc.tween(tmp).then(action).start() }) } /** * 屏蔽多点触摸 */ private canTouch = true public cannotTouch() { if (this.canTouch) { this.canTouch = false setTimeout(() => { this.canTouch = true }, 100) return false } return true } editorLoadSync(url) { if (!CC_EDITOR) return let uuid = Editor.assetdb.remote.urlToUuid('db://assets/' + url) return new Promise((resolve: (asset) => void, reject) => { cc.assetManager.loadAny({uuid}, (err, asset) => { if (err) { resolve(null) } else { resolve(asset) } }) }) } editorLoadSprite(url, node, childUrl?: string) { if (!CC_EDITOR) return ccUtils.editorLoadSync(url + '.png' + url.substring(url.lastIndexOf('/'))).then(spriteFrame => { if (childUrl) { node = cc.find(childUrl, node) } let sprite = node.getComponent(cc.Sprite) if (sprite) sprite.spriteFrame = spriteFrame }) } setPos(node, position: cc.Vec2) { node.x = position.x node.y = position.y } getPos(node) { return cc.v2(node.x, node.y) } resetAniNode(node, pos?: cc.Vec2) { node.opacity = 255 node.angle = 0 node.stopAllActions() if (pos) this.setPos(node, pos) } deepCopy(obj: any): any { const newobj: any = Array.isArray(obj) ? [] : {} for (const arr in obj) { if (typeof obj[arr] === 'object' && obj[arr] !== null) { newobj[arr] = this.deepCopy(obj[arr]) } else { newobj[arr] = obj[arr] } } return newobj } getPosAngle(x1, y1, x2, y2) { // 直角的边长 let x = Math.abs(x1 - x2) let y = Math.abs(y1 - y2) let z = Math.sqrt(x * x + y * y) let angle = Math.round((Math.asin(y / z) / Math.PI) * 180) if (y2 > y1 && x2 < x1) { angle = 180 - angle } else if (y2 < y1 && x2 < x1) { angle = 180 + angle } else if (y2 < y1 && x2 > x1) { angle = 360 - angle } else if (y2 == y1 && x2 > x1) { angle = 0 } else if (y2 == y1 && x2 < x1) { angle = 180 } else if (y2 > y1 && x2 == x1) { angle = 90 } else if (y2 < y1 && x2 == x1) { angle = 270 } return angle } tweenFloat( from: number, to: number, duration: number, onUpdate: (t: number) => void, onComplete?: Function, autoStart: boolean = true, ) { let o: Record = {_value: from} Object.defineProperty(o, 'value', { get: () => o._value, set: (v: number) => { o._value = v onUpdate && onUpdate(o._value) }, }) let tween = cc.tween(o).to(duration, {value: to}).call(onComplete) if (autoStart) { tween.start() } return tween } generateAttackPoints(r, n, isLeft, repeat) { // 计算每个角度的间隔 let angleStep = 90 / n // 存储点的坐标 let points = [] // 计算每个点的坐标 for (let i = -45; i <= 45; i += angleStep) { // 将角度转换为弧度 let radians = (i * Math.PI) / 180 // 计算当前点的 x 和 y 坐标 let x = +(r * Math.cos(radians)).toFixed(1) let y = +(r * Math.sin(radians)).toFixed(1) x = isLeft ? -x : x // 将点的坐标添加到数组中 points.push({x, y}) } points.sort((a, b) => { if (isLeft) { return a.x - b.x } else { return b.x - a.x } }) for (let i = 0; i < repeat; i++) { points = points.concat(points) } return points } isEmpty(value: any): boolean { if (value === null || value === undefined) { return true } // 判断是否是字符串 if (typeof value === 'string' && value.trim() === '') { return true } // 判断是否是数组或对象,长度为 0 则为空 if (Array.isArray(value) && value.length === 0) { return true } if (typeof value === 'object' && Object.keys(value).length === 0) { return true } return false } //初始化进度条 initProgressBar(node: cc.Node, progress: number, needSum: number, childUrl?: string) { this.setProgress(progress / needSum, node) //进度条标有进度 if (childUrl != null) { ccUtils.setLabel(progress + '/' + needSum, node, childUrl) } } getAllUrlParams(url) { // 获取 URL 中的查询参数部分 let queryString = url.split('?')[1] // 定义一个对象来存储参数 let params = {} if (!queryString) return params // 分割查询参数,以 '&' 符号分隔 let queryParams = queryString.split('&') // 遍历每个参数,并将其解析到 params 对象中 queryParams.forEach(param => { // 分割键值对,以 '=' 符号分隔 let keyValue = param.split('=') let key = decodeURIComponent(keyValue[0]) // 解码键 let value = decodeURIComponent(keyValue[1] || '') // 解码值(默认为空字符串) // 如果 params 对象中已经存在相同的键,则将值转换为数组 if (params[key]) { if (Array.isArray(params[key])) { params[key].push(value) } else { params[key] = [params[key], value] } } else { params[key] = value } }) return params } //显示呼吸灯效果 showEffectsBLN(isShow: boolean, node: cc.Node, childUrl?: string, time?: number) { if (childUrl) { node = cc.find(childUrl, node) } let hz = 0.75 if (time) hz = time if (!node) return node.active = true if (node['effect'] == isShow) return node['effect'] = isShow let t = cc.tween node.opacity = 0 node.stopAllActions() if (!isShow) { node.opacity = 0 return } let showEffect = (tmpNode: cc.Node) => { t(tmpNode) .then(t().to(hz, {opacity: 255})) .call(() => { isShow ? hideEffect(tmpNode) : (node.opacity = 0) }) .start() } let hideEffect = (tmpNode: cc.Node) => { t(tmpNode) .then(t().to(hz, {opacity: 0})) .call(() => { isShow ? showEffect(tmpNode) : (node.opacity = 0) }) .start() } showEffect(node) } } export const ccUtils = new CCUtils()