import Gamecfg from "../common/gameCfg"; import { gameMethod } from "../common/gameMethod"; import { KindItem, OrderList } from "../common/Xys"; import Config from "../Config"; import { ChatEvent } from "../data/const/EventConst"; import { AudioConst } from "../data/const/TypeConst"; import GameDataCenter from "../data/GameDataCenter"; import RollLabel, { RollLabelParam } from "../frameWork/compment/RollLabel"; import UIHelp from "../logic/ui/UIHelp"; import { FormulaCom } from "./Formula"; import GameMath from "./GameMath"; import { I18n } from "./I18nUtil"; import ColorAssembler2D from "./LabelColor"; import Load from "./Load"; export class BezierData { startPos: cc.Vec2; c1: cc.Vec2; // 起点的控制点 c2: cc.Vec2; //终点的控制点 endPos: cc.Vec2; } class UICommon { // 初始化 init() { } setIconByKindItem(node: cc.Node, item: number[], cb?: (isSucc: boolean) => void) { } // 给特定的节点加新增一个图片节点 setNodeChild(node: cc.Node, url: string, x: number = 0, y: number = 0, scale: number = 1) { if (node.getChildByName("newImgTemp")) { node.getChildByName("newImgTemp").active = true return } let child = new cc.Node() child.parent = node child.name = "newImgTemp" child.x = x child.y = y child.scale = scale Load.loadTexture(child, url) } /**设置label或富文本的文字 */ setLabel(node: cc.Node, value: string | number) { if (typeof value === 'number') { value = value.toString(); } else if (value == undefined) { value = ""; } // 文本和富文本只能二选一 if (node.getComponent(cc.RichText)) { let defaultColor = "#9F90A9"//node.color.toHEX('#rrggbb'); node.getComponent(cc.RichText).string = `${value}`; } else { node.getComponent(cc.Label).string = value; } } setLibaoLabel(node: cc.Node, orderinfo: OrderList) { this.setLabel(node, orderinfo?.desc) } //获取价格标题 getLibaoLabel(orderinfo: OrderList) { return orderinfo?.desc } setI18nLangLabel(node: cc.Node, key: string, ...args) { let str = I18n.getI18nLang(key, ...args) this.setLabel(node, str) } setI18nTextLabel(node: cc.Node, key: string, ...args: any[]) { let str = I18n.getI18nText(key, ...args) this.setLabel(node, str) } /**设置图片富文本,仅支持道具,传入itemList为道具ID,如 [1,2] */ getImageStr(str: string, itemList: KindItem[] | any[][]): string { if (itemList.length == 0) { return str } // itemList.forEach((element, itemIndex) => { // let itemCfg = GameDataCenter.item.getItemCfgBase(element) // if (itemCfg) { // str = str.replace(`{${itemIndex}}`, ``) // } // }); return str } // /**设置图片富文本,仅支持道具,传入imageList为图片资源ID,如 [1,2] @TODO 是否能够修改图片尺寸*/ // setImageRt(node: cc.Node, str: string, imageList: number[] | string[]) { // if (imageList.length == 0) { // this.setLabel(node, str) // return // } // let index = 0 // imageList.forEach(element => { // Load.getTexture(`item/${element}`, (succ: boolean, asset: cc.SpriteFrame) => { // index++ // console.log("erqasdasasdasd", index, asset) // if (succ) { // node.getComponent(cc.RichText).imageAtlas["_spriteFrames"][element] = asset // } // if (index == imageList.length) { // console.log("erqasdasasdasd33") // imageList.forEach((element, imgIndex) => { // // // str = str.replace(`{${imgIndex}}`, ``) // }); // node.getComponent(cc.RichText).string = str // } // }) // }); // } /**图片、文字 去色,0原色 1去色,递归 */ setState(node: cc.Node, state: 0 | 1, all: boolean = false) { let url = state == 0 ? "2d-sprite" : "2d-gray-sprite" if (all) { let sprites = node.getComponentsInChildren(cc.Sprite) let labels = node.getComponentsInChildren(cc.Label) sprites.forEach(sprite => { sprite.setMaterial(0, cc.Material.getBuiltinMaterial(url)) }) labels.forEach(label => { label.setMaterial(0, cc.Material.getBuiltinMaterial(url)) }) } else { if (node.getComponent(cc.Sprite) != null) { node.getComponent(cc.Sprite).setMaterial(0, cc.Material.getBuiltinMaterial(url)) } if (node.getComponent(cc.Label) != null) { node.getComponent(cc.Label).setMaterial(0, cc.Material.getBuiltinMaterial(url)) } } } /** spine动画置灰 */ setSpineState(node: cc.Node, isGrayed: boolean) { if (node.getComponent(sp.Skeleton) != null) { if (isGrayed) { cc.assetManager.loadBundle('materials', (err, bundle: cc.AssetManager.Bundle) => { bundle.load('gray-spine', cc.Material, (err, asset) => { node.getComponent(sp.Skeleton).setMaterial(0, asset); }); }); } else { node.getComponent(sp.Skeleton).setMaterial(0, cc.Material.getBuiltinMaterial("2d-spine")); } }; } /**按钮灰化,只有注册click事件,才会真正被禁用 */ setBtnGrayState(node: cc.Node, isGray: boolean) { let button = node.getComponent(cc.Button); if (!button) { return; } button.interactable = !isGray; button.enableAutoGrayEffect = isGray; } /**判断按钮是否为失效状态 */ isBtnGray(node: cc.Node): boolean { let button = node.getComponent(cc.Button); if (!button) { return false; } return !button.interactable; } /**节点注册事件 */ onRegisterEvent(node: cc.Node, callback, target, params: any = [], audio: AudioConst = AudioConst.effect_click) { if (!node) { return; } let cb = callback if (callback == null) { return } callback = function (event: cc.Event.EventTouch) { // 判断下是否有按钮组件,有的话,如果按钮是未激活状态,则不执行回调方法 let btn = node.getComponent(cc.Button) if (btn && btn.interactable == false) { return } cb.bind(target)(event, params) GameDataCenter.audio.playEffect(audio) event.stopPropagation() } node.on(cc.Node.EventType.TOUCH_END, callback, target) } /**节点取消注册事件 */ unRegisterEvent(node: cc.Node) { if (!node) { return } node.off(cc.Node.EventType.TOUCH_END); } /**获取节点的世界坐标 */ getWorldPos(node: cc.Node): cc.Vec2 { return node.convertToWorldSpaceAR(cc.Vec2.ZERO) } getWorldPosCenter(node: cc.Node): cc.Vec2 { let vec = new cc.Vec2(node.width * (0.5 - node.anchorX), node.height * (0.5 - node.anchorY)) return node.convertToWorldSpaceAR(vec) } /**设置节点在世界坐标系下的相对坐标 */ setWorldPos(node: cc.Node, pos: cc.Vec2) { let _pos = node.parent.convertToNodeSpaceAR(pos) node.x = _pos.x node.y = _pos.y } /**获取node位于target的坐标相对坐标 */ getPositionInView(node: cc.Node, target: cc.Node): { x: number, y: number } { let worldPos = node.parent.convertToWorldSpaceAR(node.position); let viewPos = target.convertToNodeSpaceAR(worldPos); return viewPos; } /**通过name从根节点递归获取node */ getTargetNodeByName(root: cc.Node, name: string) { if (root.name == name) { return root } for (let i = 0; i < root.children.length; i++) { const child = root.children[i]; let targetNode = this.getTargetNodeByName(child, name) if (targetNode != null) { return targetNode } } return null } // 递归所有子节点并且更新节点分组 setNodeAllGroups(node: cc.Node, group: string) { // 定义一个回调函数,这个函数将对每个节点执行操作 let myCallback = function (node) { // 在这里处理节点 node.group = group }; // 开始递归遍历 this.recursiveTraverseChildren(node, myCallback); } // 递归遍历所有子节点 recursiveTraverseChildren(node: cc.Node, callback: Function) { // 对当前节点执行回调函数 callback(node); // 遍历当前节点的所有子节点 for (let i = 0; i < node.children.length; i++) { // 递归遍历子节点 this.recursiveTraverseChildren(node.children[i], callback); } } // 安卓设备的适配在主activity中执行 // ios设备,需要在游戏中修改widget // 返回是否为刘海,以及刘海高度 isLiuhai(): [boolean, number] { let isLiuhai = false let height = this.getLiuhaiHeight() if (cc.sys.os == cc.sys.OS_IOS) { let _isLiuhai = jsb.reflection.callStaticMethod('SDKWrapper', 'isLiuhai:', '') isLiuhai = _isLiuhai == "1" } else if (cc.sys.os == cc.sys.OS_ANDROID) { let _isLiuhai = jsb.reflection.callStaticMethod("org/cocos2dx/javascript/AppActivity", "getIsLiuhai", "()Ljava/lang/String;"); isLiuhai = _isLiuhai == "1" } return [isLiuhai, height] } /**获取屏幕高度 */ getWinHeight() { return cc.winSize.height //- this.getLiuhaiHeight() } getLiuhaiHeight(): number { let height = 0 try { if (cc.sys.os == cc.sys.OS_IOS) { let _height = jsb.reflection.callStaticMethod('SDKWrapper', 'getLiuhaiHeight:', ''); height = Number(_height) } else if (cc.sys.os == cc.sys.OS_ANDROID) { height = jsb.reflection.callStaticMethod("org/cocos2dx/javascript/AppActivity", "getLiuhaiHeight", "()I"); } // return height } catch (error) { // return height } return height } /**获取适配scale */ getWidghtScale(height: number = cc.winSize.height): number { let defaultBili = 1920 / 1080 let realBili = height / cc.winSize.width //高宽比 return 1 + (realBili - defaultBili) } // //将字符转为表情 // TransformationStr(text) { // let richText = ""; // var arr = text.split("#"); // for (var i = 0; i < arr.length; i++) { // if (i >= 1) { // let str = arr[i].slice(0, 5) // let str1 = arr[i].slice(5) // if (gameMethod.isEmpty(Load.getEmoji(str))) { // richText += `#${str + str1}` // } else { // richText += `${str1}` // } // } else { // richText += arr[i] // } // } // return richText // } /**原表富文本模式转换成前端富文本模式*/ getConversionStr(text: string) { let tempStr = '' let str = '' let arr = text.split(''); let isColor: boolean = false let isValue: boolean = true for (let i = 0; i < arr.length; i++) { if (arr[i] == '<') { tempStr = '' isColor = true isValue = false } if (isColor) { tempStr += arr[i] } if (isValue) { str += arr[i] } if (tempStr == '
') { tempStr = '' str += tempStr isColor = false isValue = true } } return str } /**原表成前端*/ getTypeStr(text: string) { let str = text.replace(/
/g, '') return str } // getEpsStrArr(eps: XyS.Eps | EpsPet) { // let bak: { // ek: string, // num: number, // ekShow: string, //名称 // numShow: number, //展示数值 // suffix: string, //后缀 // }[] = [] // //遍历配置 // for (const key in eps) { // let arrt = gameCfg.attr.pool[`${key}_`] // if (!gameMethod.isEmpty(arrt)) { // switch (arrt.tip) { // case 'atk'://攻击 角色的物理攻击能力 // case 'def_p'://物防 角色抵抗物理攻击的能力 // case 'def_s'://法防 角色抵抗法术攻击的能力 // case 'hp_max'://生命 角色的生命上限值 // case 'hp'://生命 角色当前的生命值 // case 'speed'://速度 决定单位在战斗中的出手频率 // case 'def'://防御 角色抵抗物理和法术攻击的能力 // //数值直接展示 // bak.push({ // ek: arrt.tip, // num: eps[arrt.tip], // ekShow: arrt.name, //名称 // numShow: eps[arrt.tip], //展示数值 // suffix: '%', //后缀 // }) // break; // case 'hit_rate'://命中 成功造成伤害的概率 // case 'dodge_rate'://闪躲 躲避伤害的概率 // case 'crit_rate'://暴击率 攻击方产生暴击的概率 // case 'crit_ratio'://暴伤 暴击产生时,伤害的变化比例 // case 'hit_magic'://控制 增强技能效果命中的比例 // case 'dodge_magic'://抗控 抵抗技能效果命中的比例 // case 'tenacity'://抗暴 受击方被暴击概率降低 // case 'atk_per'://攻击 百分比增加单位的攻击力 // case 'def_per'://防御 百分比增加单位的物理防御和法术防御 // case 'hp_max_per'://生命 百分比增加单位的生命上限 // case 'dam'://伤害加成 千分比增加造成的伤害 // case 'res'://免伤 千分比减少受到的伤害 // case 'cure'://治疗 千分比增减治疗别人的效果 // case 'be_cure'://受疗 千分比增减受到的治疗效果 // case 'dam_p'://物伤 千分比增加造成的物理伤害 // case 'dam_s'://法伤 千分比增加造成的法术伤害 // case 'res_p'://物免 千分比减少受到的物理伤害 // case 'res_s'://法免 千分比减少受到的法术伤害 // case 'speed_per'://速度 决定单位在战斗中的出手频率 // case 'def_p_per'://物防 千分比增加单位的物理防御 // case 'def_s_per'://法防 千分比增加单位的法术防御 // case 'toughness'://韧性 千分比减免持续伤害类负面效果带来的伤害 // //数值百分比加成 // bak.push({ // ek: arrt.tip, // num: eps[arrt.tip], // ekShow: arrt.name, //名称 // numShow: eps[arrt.tip] / 10, //展示数值 // suffix: '%', //后缀 // }) // break; // } // } // } // return bak // } /** 打字效果 */ typingAni(label: cc.Label | cc.RichText, text: string, time: number = 1 / 60, cb: Function = () => { }) { if (!text || text.length <= 0) { cb() return } let html = ''; let tempStr = '' let tempColorStr = '' let str: string[] = [] let arr = text.split(''); let len = arr.length; let step = 0; let isColor: boolean = false let isValue: boolean = false if (label instanceof cc.RichText) { for (let i = 0; i < len; i++) { if (arr[i] == '<') { tempColorStr = '' isValue = false } if (isValue) { tempStr = `${tempColorStr}${arr[i]}` str.push(tempStr) tempStr = '' } if (arr[i] == '#') { isColor = true } if (arr[i] == '>' && tempColorStr != '') { isColor = false isValue = true tempColorStr = `` } if (isColor) { tempColorStr += arr[i] } } arr = str len = str.length } let typingFunc = () => { if (step >= len) { label.unschedule(typingFunc); cb && cb(); } else { html += arr[step]; label.string = html } step++ } label.schedule(typingFunc, time, len) } /** 富文本打字效果 */ typingRich(label: cc.RichText, text: string, wordsNum: number, time: number = 1 / 60, cb: Function = () => { }) { if (!text || text.length <= 0) { cb() return } let str = text; let charArr = str.replace(/<.+?\/?>/g, '').split(''); let tempStrArr = [str], curstr = str; for (let i = charArr.length; i > 1; i--) { let lastIdx = curstr.lastIndexOf(charArr[i - 1]); let prevstr = curstr.slice(0, lastIdx); let nextstr = curstr.slice(lastIdx + 1, curstr.length); if ((i - 1) % wordsNum == 0) tempStrArr.push(prevstr + nextstr); curstr = prevstr + nextstr } let step = 0, len = tempStrArr.length; let typingFunc = () => { if (step >= len) { label.unschedule(typingFunc); cb && cb(); } else { label.string = tempStrArr.pop() } step++ } label.schedule(typingFunc, time, len) typingFunc() } // /** // * @description:给需要点击回调的RichText添加回调脚本 // * @param {type} node 包含RichText组件的Node // * @param {type} component 需要处理点击事件的脚本 // * @return {type} // */ // createRichTextCallback(node, component) { // if (gameMethod.isEmpty(node)) { // console.log("node must be cc.Node!!!"); // return; // } // let richText = node.getComponent(cc.RichText); // if (gameMethod.isEmpty(richText)) { // console.log("node must have RichText component!!!"); // return; // } // let st = node.addComponent(RichTextEvent); // st.setObject(component); // } // /** // * @description:获取技能buff描述 // * @param {type} buffId 需要处理点击事件的脚本 // * @return {type} // */ // getBuffDesc(buffId: string, withTitle: boolean = true): string { // let conf = gameCfg.skill_all_buff.getItem(buffId) // if (conf == null) { // console.log(" buff info is not exist!!!"); // return "" // } // if (withTitle) { // return `[${conf.name}]\n${conf.desc}` // } // return conf.desc // } //给 spine 重新拷贝一份 skeletonData 数据,让他们不重复 public copySpine(skeleton: sp.Skeleton) { let date = new Date(); // 记录当前播放的动画 const animation = skeleton.animation const spdata = skeleton.skeletonData; let copy = new sp.SkeletonData(); cc.js.mixin(copy, spdata); // @ts-ignore copy._uuid = spdata._uuid + "_" + date.getTime() + "_copy"; let old = copy.name; let newName = copy.name + "_copy"; copy.name = newName; copy.atlasText = copy.atlasText.replace(old, newName); // @ts-ignore copy.textureNames[0] = newName + ".png"; // @ts-ignore copy.init && copy.init(); skeleton.skeletonData = copy; // 继续播放的动画,不然会停止 skeleton.setAnimation(0, animation, true); } /** * 用外部图片局部换装 * @param skeleton 骨骼动画 * @param slotName 需要替换的插槽名称 * @param texture 外部图片 */ public changeSlot(skeleton: sp.Skeleton, slotName: string, texture: cc.Texture2D) { if (cc.sys.isBrowser) { const slot: sp.spine.Slot = skeleton.findSlot(slotName); const attachment: sp.spine.RegionAttachment = slot.getAttachment() as sp.spine.RegionAttachment; if (!slot || !attachment) { cc.error('updatePartialSkin') return; } // @ts-ignore const skeletonTexture = new sp.SkeletonTexture({}); skeletonTexture.setRealTexture(texture); let region: sp.spine.TextureAtlasRegion = attachment.region as sp.spine.TextureAtlasRegion; region.u = 0; region.v = 0; region.u2 = 1; region.v2 = 1; region.width = texture.width * 5; region.height = texture.height * 5; region.originalWidth = texture.width * 5; region.originalHeight = texture.height * 5; region.rotate = false; region.texture = skeletonTexture; attachment.width = texture.width * 5; attachment.height = texture.height * 5; attachment.region = region; attachment.setRegion && attachment.setRegion(region) attachment.updateOffset && attachment.updateOffset(); // attachment.updateUVs && attachment.updateUVs(attachment); slot.setAttachment(attachment); } else { // @ts-ignore const jsbTex = new middleware.Texture2D(); jsbTex.setPixelsHigh(texture.height * 5); jsbTex.setPixelsWide(texture.width * 5); jsbTex.setNativeTexture(texture.getImpl()); // @ts-ignore skeleton.updateRegion(slotName, jsbTex); } // skeleton 如果使用了缓存模式则需要刷新缓存 skeleton.invalidAnimationCache(); } // 移除所有子节点 destoryAllChildren(node: cc.Node) { node.children.forEach(child => { child.destroy() }) } // 根据品质获取颜色 getColorByPz(pinzhi: number | string) { // 品质1·8的,9瑶光的自己拼 switch (Number(pinzhi)) { case 1: return '#B3B3B3' case 2: return '#81B58B' case 3: return '#7CACCF' case 4: return '#A77CC5' case 5: return '#E6A76A' case 6: return '#E7C45A' case 7: return '#D45F59' case 8: return '#76DEE4' case 9: return '#EB91F0' case 10: return '#FF66D9' case 11: return '#98F22E' case 12: return '#FFCC19' default: return '#FFFFFF' } } // // 根据品质获取颜色 // setColorTxtByPz(txt:string,pinzhi: number | string) { // let color = this.getColorByPz(pinzhi); // let str = `[color=${color}]` // } // 根据品质获取名称 getNameByPz(pinzhi: number | string) { return I18n.getI18nLang(`zhenfa_pinzhi_name_${pinzhi}`); } getEnoughColor(isenough: boolean) { return isenough ? "6fdf89" : "E45849" } // 修正文字布局 fixName(msg: string): string { // if (msg.length >= 4) { // return msg // } // let out = "" // for (let index = 0; index < msg.length; index++) { // out += msg[index] // // 添加空格 // if (index + 1 == msg.length) { // break // } // if (msg.length == 2) { // out += " " // } else if (msg.length == 3) { // out += " " // } // } // return out; return msg; } // cocos画布的节点位置信息转化为微信画布位置信息 getWxBtnPos(node: fgui.GObject): { left: number, top: number, width: number, height: number } { let visibleSize = cc.view.getVisibleSize(); // console.log("==visibleSize==", visibleSize.width, visibleSize.height) // console.log("==position==", node.node.position) // let worldPos = FormulaCom.getWorldPos(node.node) let worldPos = fgui.GRoot.inst.localToGlobal(node.x, node.y) // console.log("==fgui worldPos==", fgui.GRoot.inst.localToGlobal(node.x, node.y)) // console.log("==cc worldPos==", FormulaCom.getWorldPos(node.node)) //获取系统信息 let wx_size = wx?.getSystemInfoSync(); // console.log("==wx_size==", wx_size.screenWidth, wx_size.screenHeight) //计算实际大小和可见区域尺寸的比例(这里以宽度为准) let size_scale_width = wx_size.screenWidth / visibleSize.width; let size_scale_height = wx_size.screenHeight / Config.realHeight; //计算创建用户信息按钮需要的属性,考虑锚点 let offsetX = node.width * node.scaleX * (node.node.anchorX / 1) let offsetY = node.height * node.scaleY * ((1 - node.node.anchorY) / 1) // console.log("==anchorX==", node.node.anchorX) // console.log("==anchorY==", node.node.anchorY) // console.log("==offsetX==", offsetX) // console.log("==offsetY==", offsetY) let left = (worldPos.x - offsetX) * size_scale_width // (this.ui.btnUser.x + visibleSize.width / 2 - this.ui.btnUser.width / 2) * size_scale_width; let top = (worldPos.y + node.height / 2) * size_scale_height //fgui // let top = wx_size.screenHeight - (worldPos.y - offsetY - Config.safeAreaRect.y / 2 - node.height / 2) * size_scale_height //cc let width = node.width * size_scale_width * node.scaleX let height = node.height * size_scale_height * node.scaleY // let y = (Math.abs(this.ui.btnUser.y) - this.ui.btnUser.height / 2) * size_scale_width; // let width = this.ui.btnUser.width * size_scale_width; // let height = this.ui.btnUser.height * size_scale_width; // console.log("==left==", left) // console.log("==top==", top) // console.log("==width==", width) // console.log("==height==", height) return { left: left, top: top, width: width, height: height } } btnShake(node: cc.Node) { let tween = cc.tween(node) .delay(2) .to(0.05, { angle: -15 }) .to(0.1, { angle: 15 }) .to(0.1, { angle: -15 }) .to(0.1, { angle: 15 }) .to(0.05, { angle: 0 }) cc.tween(node).repeatForever(tween).start() } /** * 抛物线飞行 //开始坐标 //中间坐标(高度) //结束坐标 * p0是起始点 * p1是中点 * p2是终点 * p1通过p2和p0的Xaxis相减获得 * height是p0加上的高度 * t 的取值范围是0-1(整个过程的归一化 */ paowuxian(parm: { node: cc.Node; s: { x: number; y: number }; e: { x: number; y: number }; h: number; tadd?: number; //时间加减速比例 默认100 cbk?: Function; }) { //中间点 let mv2 = cc.v2(parm.s.x + (parm.e.x - parm.s.x) / 2, (parm.s.y + parm.e.y) / 2 + parm.h); //设置时间 let time = Math.sqrt(Math.abs(mv2.y - parm.s.y)) + Math.sqrt(Math.abs(mv2.y - parm.e.y)); time /= 50; if (parm.tadd != null) { time = (time * parm.tadd) / 100; } //起始点 parm.node.x = parm.s.x; parm.node.y = parm.s.y; cc.tween(parm.node) .bezierTo(time, mv2, mv2, cc.v2(parm.e.x, parm.e.y)) .call(() => { if (parm.cbk != null) { parm.cbk(); } }) .start(); } //抛物线 + 落地弹弹弹 paowuxian_tan( parm: { node: cc.Node; s: { x: number; y: number }; e: { x: number; y: number }; h: number; jian: number; //弹的衰减比例 100 不衰减 tadd?: number; //时间加减速比例 默认100 cbk?: Function; }, t?: number //当前第几次弹 初次是0 不算弹的 ) { //跳 this.paowuxian({ node: parm.node, s: parm.s, e: parm.e, h: parm.h, tadd: parm.tadd, cbk: () => { //第几次弹 if (t == null) { t = 0; } t += 1; //当前衰减比例 let pers = Math.pow(parm.jian / 100, t); if (pers < 0.1) { //不再弹了 if (parm.cbk != null) { parm.cbk(); return; } } //目标 let _h = parm.h * pers; //跳的高度 衰减 let _e = { x: parm.e.x + (parm.e.x - parm.s.x) * pers, //跳的宽度 衰减 y: parm.e.y, //水平坐标不变 }; //调用自己 this.paowuxian_tan( { node: parm.node, s: parm.e, //开始 = 上一次的结束 e: _e, h: _h, jian: parm.jian, tadd: parm.tadd, cbk: parm.cbk, }, t ); }, }); } // 引擎自带的贝塞尔曲线运动 runBezierAct(actNode: cc.Node, duration: number, bezierDatas: BezierData[]) { if (bezierDatas.length <= 0) return let tw = cc.tween() for (let i = 0; i < bezierDatas.length; ++i) { tw.bezierTo(duration, bezierDatas[i].c1, bezierDatas[i].c2, bezierDatas[i].endPos) } actNode.setPosition(bezierDatas[0].startPos) tw.clone(actNode).start() } // 匀速贝塞尔曲线运动 runUniformBezierAct(actNode: cc.Node, duration: number, bezierDatas: BezierData[], onComplete?: Function) { if (bezierDatas.length <= 0) return let tw = cc.tween() let allBezierPos: cc.Vec2[] = [] for (let i = 0; i < bezierDatas.length; ++i) { let posArr = [bezierDatas[i].startPos, bezierDatas[i].c1, bezierDatas[i].c2, bezierDatas[i].endPos] allBezierPos = allBezierPos.concat(this._caculateBezierPoint(posArr)) } let totalLineLen = this._caculateBezierLength(allBezierPos) let speed = totalLineLen / duration let isCanBezier = false for (let i = 1; i < allBezierPos.length; ++i) { let dis = cc.v2(allBezierPos[i].x - allBezierPos[i - 1].x, allBezierPos[i].y - allBezierPos[i - 1].y).len() if (dis > 0) { // 这里过滤掉两段贝塞尔曲线首位连接的点 let t = dis / speed tw.to(t, { position: allBezierPos[i] }) .call(() => { let angle = FormulaCom.getAngleByPos(allBezierPos[i - 1].x, allBezierPos[i - 1].y, allBezierPos[i].x, allBezierPos[i].y) // let angle = FormulaCom.getAngle(allBezierPos[i - 1],allBezierPos[i]) // console.log("angle = ",angle) actNode.angle = angle }) isCanBezier = true } } if (!isCanBezier) { console.error("allBezierPos.length == 0") return } actNode.setPosition(allBezierPos[0]) tw.clone(actNode).call(() => { onComplete && onComplete(); }).start() } // 计算所有贝塞尔曲线的点 private _caculateBezierPoint(posArr: cc.Vec2[]) { let allBezierPos = [] let allDis = cc.Vec2.distance(posArr[0], posArr[3]) let gapCount = Math.ceil(allDis / 10)//距离5像素为一点 let gap = 1 / gapCount // 每次迭代步长,这个值越小越精细,但是效率越低,这里迭代300次已经够了 for (let i = 0; i <= 1; i += gap) { let pos = this._caculateBezierP(posArr, i) allBezierPos.push(pos) } return allBezierPos } // 计算贝塞尔曲线的长度 private _caculateBezierLength(allBezierPos: cc.Vec2[]) { let totalLineLen = 0 for (let i = 1; i < allBezierPos.length; ++i) { let dis = cc.v2(allBezierPos[i].x - allBezierPos[i - 1].x, allBezierPos[i].y - allBezierPos[i - 1].y).len() totalLineLen += dis } return totalLineLen } /** * 计算三阶贝塞尔在 t时刻 的位置 * @param p 三阶贝塞尔的四个点,数组对应为 0起点,1起点控制点,2终点控制点,3终点 * @param t 传入0-1的值,一个时间的迭代过程 * @returns */ private _caculateBezierP(p: cc.Vec2[], t: number): cc.Vec2 { // 三阶贝塞尔运算 let bezierP: cc.Vec2 = cc.v2() bezierP.x = Math.floor(Math.pow(1 - t, 3) * p[0].x + 3 * t * Math.pow(1 - t, 2) * p[1].x + 3 * Math.pow(t, 2) * (1 - t) * p[2].x + Math.pow(t, 3) * p[3].x); bezierP.y = Math.floor(Math.pow(1 - t, 3) * p[0].y + 3 * t * Math.pow(1 - t, 2) * p[1].y + 3 * Math.pow(t, 2) * (1 - t) * p[2].y + Math.pow(t, 3) * p[3].y); return bezierP; } /** 判断隐藏礼包是否开启 * @param ycgift 隐藏礼包数据 * @param dc 档次 * @param buyNum 购买次数 * @param buyCons 购买金额 */ checkYcGiftOpen(tiaojian: number[], buyNum: number, buyCons: number) { if (gameMethod.isEmpty(tiaojian)) return false; //有配置全局充值金额并且全局充值大于固定金额显示 if (!gameMethod.isEmpty(tiaojian[2]) && GameDataCenter.sevBack?.userInfo?.a?.iscz >= tiaojian[2]) { return true; } // 隐藏礼包条件判断 if (tiaojian[0] > 0 && tiaojian[1] <= 0) { // 第一种 只判断次数 if (buyNum >= tiaojian[0]) { return true; } } else if (tiaojian[0] <= 0 && tiaojian[1] > 0) { // 第二种 只判断金额 if (buyCons >= tiaojian[1]) { return true; } } else if (tiaojian[0] > 0 && tiaojian[1] > 0) { // 第三种 判断次数和金额 满足一种 if (buyNum >= tiaojian[0] || buyCons >= tiaojian[1]) { return true; } } return false; } // 获取属性名跟值 *传入值 "atk":10 返回 [攻击, 10] getEpsNameValue(key: string, value: number): string[] { let epsCfg = Gamecfg.userEp.getItem(key); value = value ? value : 0; if (gameMethod.isEmpty(epsCfg)) { // console.log('配置错误===', key) return [key, value + ""]; } // return [this.fixName(epsCfg.name), (epsCfg.isPer ? `${value / 100}%` : value + "") || "0"]; return [this.fixName(I18n.getUserEpName(epsCfg.key)), epsCfg.isPer ? `${value / 100}%` : value + ""] } /** * UI节点转换到目标节点下的坐标 * @param node 节点 * @param targetNode 目标节点 * @returns {转换后的坐标的点|Point} */ transPos(node: cc.Node, targetNode: cc.Node): cc.Vec2 { //转世界坐标 var endGlobalPos = this.getWorldPos(node); if (!endGlobalPos) return null; //再转局部坐标 var endPos = targetNode.convertToNodeSpaceAR(endGlobalPos); return endPos; } // 剪贴板复制功能 public webCopyString(str: string) { // console.log('复制ing'); var input = str + ''; const el = document.createElement('textarea'); el.value = input; el.setAttribute('readonly', ''); // el.style.contain = 'strict'; el.style.position = 'absolute'; el.style.left = '-9999px'; el.style.fontSize = '12pt'; // Prevent zooming on iOS const selection = getSelection(); var originalRange = null; if (selection.rangeCount > 0) { originalRange = selection.getRangeAt(0); } document.body.appendChild(el); el.select(); el.selectionStart = 0; el.selectionEnd = input.length; var success = false; try { success = document.execCommand('copy'); } catch (err) { } document.body.removeChild(el); if (originalRange) { selection.removeAllRanges(); selection.addRange(originalRange); } if (success) { UIHelp.ShowTips(I18n.getI18nText('common_copy_sucess')); } else { UIHelp.ShowTips(I18n.getI18nText('common_copy_fail')); } return success; } /** * 设置滚动变化的数字文本,不要再任何虚拟列表内使用 * @param obj 需要控制的文本组件 * @param num 变化的目标数字 * @param cb 实时变化的数字值,自己在内部写需要执行和改变的文本(如果不传则直接对obj.text或这obj.title赋值 * @param param 参数 */ SetLabelRoll(obj: fairygui.GTextField | fairygui.GLabel | fairygui.GRichTextField, num: number, cb: (val: number) => void = null, param: RollLabelParam = null) { let rolllabel = obj.node.getComponent(RollLabel) if (rolllabel == null) { rolllabel = obj.node.addComponent(RollLabel) rolllabel.obj = obj } rolllabel.setData(num, cb, param) } /** * 上漂文本 * @param func 改变文本图标的方法 * @param node 位置节点定位用 * @param view 挂载上漂节点用的页面 * @param cache 缓存 * @param randomX * @param randomY * @param pac * @param popItem */ PopTips(func: (obj: fairygui.GLabel) => void, node: fairygui.GComponent, view: fairygui.GComponent, cache: PopTipsCache[], randomX: number[] | number = null, randomY: number[] | number = null, pac: string = "Common", popItem: string = "PopTips") { let data: PopTipsCache = null for (let i of cache) { if (i.obj.visible == false) { data = i break } } if (data == null) { let obj = fgui.UIPackage.createObject(pac, popItem) as fairygui.GLabel view.addChild(obj) data = new PopTipsCache(obj) cache.push(data) } if (typeof randomX == "number") { data.obj.x = node.x + randomX } else if (randomX) { let left = randomX[0] let right = randomX[1] let randomposx = GameMath.getRandomNum(left, right) data.obj.x = node.x + randomposx } else { data.obj.x = node.x } if (typeof randomY == "number") { data.obj.y = node.y + randomY } else if (randomY) { let left = randomY[0] let right = randomY[1] let randomposy = GameMath.getRandomNum(left, right) data.obj.y = node.y + randomposy } else { data.obj.y = node.y } func(data.obj) data.startTween() } /** 数字转中文 */ NumToChinese(num) { if (num < 0 || num > 100) { return "输入的数字必须在0到100之间"; } const units = ['', '十', '百', '千']; // 可以根据需要扩展 const chineseNums = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; let str = ''; let digit; // 处理个位数 digit = num % 10; if (digit !== 0) { str = chineseNums[digit]; } // 处理十位数 num = Math.floor(num / 10); if (num !== 0) { if (num == 1) { str = '十' + str; } else { str = chineseNums[num] + '十' + str; } } // 如果需要处理更大的数字,可以继续添加逻辑 // 移除开头的'零' str = str.replace(/^零+/, ''); if (str === '') { str = '零'; } return str; } /** 圆上指定角度一点 */ GetPointOnCircle(radius: number, degree: number, center: cc.Vec2): cc.Vec2 { let angle = this.DegreeToAngle(degree); // 使用三角函数计算圆上该角度对应的点的x和y坐标 const x = center.x + radius * Math.cos(angle); const y = center.y + radius * Math.sin(angle); // 返回该点的坐标 return cc.v2(x, y); } /** 圆上任意一点 */ GetRandomPointOnCircle(radius: number, centerX = 0, centerY = 0): cc.Vec2 { // 生成一个0到2π之间的随机角度(弧度) const angle = Math.random() * 2 * Math.PI; // 使用三角函数计算圆上该角度对应的点的x和y坐标 const x = centerX + radius * Math.cos(angle); const y = centerY + radius * Math.sin(angle); // 返回该点的坐标 return cc.v2(x, y); } /** 圆上角度范围一点 */ GetRandomPointOnAngle(radius: number, rangeAngle: number, center: cc.Vec2, point: cc.Vec2): cc.Vec2 { // 生成一个0到2π之间的随机角度(弧度) // const angle = Math.random() * 2 * Math.PI; const dx = point.x - center.x; const dy = point.y - center.y; const angleRadians = Math.atan2(dy, dx); let angle = this.AngleToDegree(angleRadians); let newAngle = GameMath.getRandomNum(rangeAngle, rangeAngle * 2) - rangeAngle; angle += newAngle; let degree = this.DegreeToAngle(angle); // 使用三角函数计算圆上该角度对应的点的x和y坐标 const x = center.x + radius * Math.cos(degree); const y = center.y + radius * Math.sin(degree); // 返回该点的坐标 return cc.v2(x, y); } /** 圆上指定角度一点 */ GetCirclePointOnAngle(radius: number, diffAngle: number, center: cc.Vec2, point: cc.Vec2): cc.Vec2 { // 生成一个0到2π之间的随机角度(弧度) // const angle = Math.random() * 2 * Math.PI; const dx = point.x - center.x; const dy = point.y - center.y; const angleRadians = Math.atan2(dy, dx); let angle = this.AngleToDegree(angleRadians); let degree = angle + diffAngle; let newAngle = this.DegreeToAngle(degree); // 使用三角函数计算圆上该角度对应的点的x和y坐标 const x = center.x + radius * Math.cos(newAngle); const y = center.y + radius * Math.sin(newAngle); // 返回该点的坐标 return cc.v2(x, y); } /** 圆上分割角度后的坐标点 */ GetCirclePointDivideAngle(radius: number, divideCount: number, center: cc.Vec2): cc.Vec2[] { // 生成一个0到2π之间的随机角度(弧度) let per = divideCount / divideCount; let posArr = []; for (let i = 0; i < divideCount; i++) { let degree = per * i; // 使用三角函数计算圆上该角度对应的点的x和y坐标 const x = center.x + radius * Math.cos(degree); const y = center.y + radius * Math.sin(degree); posArr.push(cc.v2(x, y)) } return posArr; } /** 圆上指定弧长的点 */ GetCircleAngleLenPoint(radius: number, len: number, center: cc.Vec2, point: cc.Vec2, index: number): cc.Vec2 { const dx = point.x - center.x; const dy = point.y - center.y; const angleRadians = Math.atan2(dy, dx); let degree = this.AngleToDegree(angleRadians); let totalLen = 2 * Math.PI * radius; let needDegree = len / totalLen * 360;//弧长对应角度 let newDegree = degree + index * needDegree; let newAngle = this.DegreeToAngle(newDegree); // 使用三角函数计算圆上该角度对应的点的x和y坐标 const x = center.x + radius * Math.cos(newAngle); const y = center.y + radius * Math.sin(newAngle); // 返回该点的坐标 return cc.v2(x, y); } /** 弧度转角度 */ public AngleToDegree(angleRadians) { const angleDegrees = angleRadians * (180 / Math.PI); return angleDegrees } /** 角度转弧度 */ public DegreeToAngle(degree: number) { const angle = degree / (180 / Math.PI); return angle; } /** 获取两点角度 */ public GetTwoPointAngle(center: cc.Vec2, point: cc.Vec2) { const dx = point.x - center.x; const dy = point.y - center.y; const angleRadians = Math.atan2(dy, dx); let angle = this.AngleToDegree(angleRadians); return angle; } /** 求多个点的中心点 */ public CalculateCentroid(points: cc.Vec2[]): cc.Vec2 { // 初始化x和y的总和 let sumX = 0; let sumY = 0; // 遍历所有点,累加x和y的坐标 points.forEach(point => { sumX += point.x; sumY += point.y; }); // 计算平均x和y坐标 let centroidX = sumX / points.length; let centroidY = sumY / points.length; // 返回中心点坐标 return cc.v2(centroidX, centroidY); } } export class PopTipsCache { obj: fairygui.GLabel tween1: cc.Tween; tween2: cc.Tween; constructor(obj: fairygui.GLabel) { this.obj = obj } startTween() { this.obj.alpha = 0 this.obj.visible = true let y = this.obj.y - 120; this.tween1 = cc.tween(this.obj).to(1, { y: y, }).call(() => { this.obj.visible = false }) this.tween2 = cc.tween(this.obj).to(0.2, { alpha: 1, }).delay(0.6).to(0.2, { alpha: 0, }) this.tween1.start() this.tween2.start() } clearTween() { this.tween1?.stop() this.tween2?.stop() this.obj.visible = false; } } export let uiCommon = new UICommon()