import * as cc from "cc"; import { Vec2 } from "cc"; import { Color } from "cc"; import { _decorator, ITweenOption, Node, sp, Tween, tween, UIOpacity, UITransform, Vec3 } from "cc"; const { ccclass, property } = _decorator; declare const pako: any; @ccclass("UmUtil") export class UmUtil { /** * !#zh 拷贝object。 */ public static clone(sObj: any) { if (sObj === null || typeof sObj !== "object") { return sObj; } let s: any = {}; if (sObj.constructor === Array) { s = []; } for (const i in sObj) { if (sObj.hasOwnProperty(i)) { s[i] = this.clone(sObj[i]); } } return s; } /** * 将object转化为数组。 */ public static objectToArray(srcObj: any) { const resultArr = []; // to array for (let key in srcObj) { if (!srcObj.hasOwnProperty(key)) { continue; } resultArr.push(srcObj[key]); } return resultArr; } /** * !#zh 将数组转化为object。 */ public static arrayToObject(srcObj: any, objectKey: any) { const resultObj: any = {}; // to object for (let key in srcObj) { if (!srcObj.hasOwnProperty(key) || !srcObj[key][objectKey]) { continue; } resultObj[srcObj[key][objectKey]] = srcObj[key]; } return resultObj; } // 根据权重,计算随机内容 public static getWeightRandIndex(weightArr: any, totalWeight: number) { const randWeight = Math.floor(Math.random() * totalWeight); let sum = 0; let weightIndex = 0; for (weightIndex; weightIndex < weightArr.length; weightIndex++) { sum += weightArr[weightIndex]; if (randWeight < sum) { break; } } return weightIndex; } /** * 从n个数中获取m个随机数 * @param {Number} n 总数 * @param {Number} m 获取数 * @returns {Array} array 获取数列 */ public static getRandomNFromM(n: number, m: number) { const array: number[] = []; let intRd = 0; let count = 0; while (count < m) { if (count >= n + 1) { break; } intRd = this.getRandomInt(0, n); let flag = 0; for (let i = 0; i < count; i++) { if (array[i] === intRd) { flag = 1; break; } } if (flag === 0) { array[count] = intRd; count++; } } return array; } public static getRandomInt(min: number, max: number) { const r = Math.random(); const rr = r * (max - min + 1) + min; return Math.floor(rr); } public static getStringLength(render: string) { const strArr = render; let len = 0; for (let i = 0, n = strArr.length; i < n; i++) { const val = strArr.charCodeAt(i); if (val <= 255) { len = len + 1; } else { len = len + 2; } } return Math.ceil(len / 2); } /** * 判断传入的参数是否为空的Object。数组或undefined会返回false * @param obj */ public static isEmptyObject(obj: any) { let result = true; if (obj && obj.constructor === Object) { for (const key in obj) { if (obj.hasOwnProperty(key)) { result = false; break; } } } else { result = false; } return result; } public static formatNum(num: number) { // 0 和负数均返回 NaN。特殊处理。 if (num <= 0) { return '0'; } const k = 1000; const sizes = ['', '', 'K', 'M', 'B']; const i = Math.round(Math.log(num) / Math.log(k)); return parseInt((num / (Math.pow(k, i - 1 < 0 ? 0 : i - 1))).toString(), 10) + sizes[i]; } public static formatMoneyWithFraction(num: number, fraction?: number) { return num.toFixed(fraction).replace(/\B(?=(\d{3})+(?!\d))/g, ","); } /** * 判断是否是新的一天 * @param {Object|Number} dateValue 时间对象 todo MessageCenter 与 pve 相关的时间存储建议改为 Date 类型 * @returns {boolean} */ public static isNewDay(dateValue: any) { // todo:是否需要判断时区? const oldDate = new Date(dateValue); const curDate = new Date(); const oldYear = oldDate.getFullYear(); const oldMonth = oldDate.getMonth(); const oldDay = oldDate.getDate(); const curYear = curDate.getFullYear(); const curMonth = curDate.getMonth(); const curDay = curDate.getDate(); if (curYear > oldYear) { return true; } else { if (curMonth > oldMonth) { return true; } else { if (curDay > oldDay) { return true; } } } return false; } public static getPropertyCount(o: Object) { let n, count = 0; for (n in o) { if (o.hasOwnProperty(n)) { count++; } } return count; } /** * 返回一个差异化数组(将array中diff里的值去掉) * @param array * @param diff */ public static difference(array: any, diff: any) { const result: number[] = []; if (array.constructor !== Array || diff.constructor !== Array) { return result; } const length = array.length; for (let i = 0; i < length; i++) { if (diff.indexOf(array[i]) === -1) { result.push(array[i]); } } return result; } // 模拟传msg的uuid public static simulationUUID() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } public static trim(str: string) { return str.replace(/(^\s*)|(\s*$)/g, ""); } /** * 判断当前时间是否在有效时间内 * @param {String|Number} start 起始时间。带有时区信息 * @param {String|Number} end 结束时间。带有时区信息 */ public static isNowValid(start: string | number, end: string | number) { const startTime = new Date(start); const endTime = new Date(end); let result = false; if (startTime.getDate() + '' !== 'NaN' && endTime.getDate() + '' !== 'NaN') { const curDate = new Date(); result = curDate < endTime && curDate > startTime; } return result; } public static getDeltaDays(start: string | number, end: string | number) { const startData = new Date(start); const endData = new Date(end); const startYear = startData.getFullYear(); const startMonth = startData.getMonth() + 1; const startDate = startData.getDate(); const endYear = endData.getFullYear(); const endMonth = endData.getMonth() + 1; const endDate = endData.getDate(); start = new Date(startYear + '/' + startMonth + '/' + startDate + ' GMT+0800').getTime(); end = new Date(endYear + '/' + endMonth + '/' + endDate + ' GMT+0800').getTime(); const deltaTime = end - start; return Math.floor(deltaTime / (24 * 60 * 60 * 1000)); } public static getMin(array: any) { let result = 0; if (array.constructor === Array) { const length = array.length; for (let i = 0; i < length; i++) { if (i === 0) { result = Number(array[0]); } else { result = result > Number(array[i]) ? Number(array[i]) : result; } } } return result; } public static formatTwoDigits(time: number) { return (Array(2).join('0') + time).slice(-2); } public static formatDate(date: Date, fmt: string) { const o: { [name: string]: number } = { "M+": date.getMonth() + 1, //月份 "d+": date.getDate(), //日 "h+": date.getHours(), //小时 "m+": date.getMinutes(), //分 "s+": date.getSeconds(), //秒 "q+": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); for (const k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (`${o[k]}`) : ((`00${o[k]}`).substr(("" + o[k]).length))); return fmt; } /** * */ public static getDay(time: number, splitChar: string = '-') { const date = new Date(time); return date.getDate() + splitChar + (date.getMonth() + 1) + splitChar + date.getFullYear(); } public static getTime(): number { return (new Date()).getTime(); } /** * 格式化钱数,超过10000 转换位 10K 10000K 转换为 10M */ public static formatMoney(money: number) { const arrUnit = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'B', 'N', 'D']; let strValue = ''; for (let idx = 0; idx < arrUnit.length; idx++) { if (money >= 10000) { money /= 1000; } else { strValue = Math.floor(money) + arrUnit[idx]; break; } } if (strValue === '') { strValue = Math.floor(money) + 'U'; //超过最大值就加个U } return strValue; } /** * 根据剩余秒数格式化剩余时间 返回 HH:MM:SS * @param {Number} leftSec */ public static formatTimeForSecond(leftSec: number) { let timeStr = ''; const sec = leftSec % 60; let leftMin = Math.floor(leftSec / 60); leftMin = leftMin < 0 ? 0 : leftMin; const hour = Math.floor(leftMin / 60); const min = leftMin % 60; if (hour > 0) { timeStr += hour > 9 ? hour.toString() : '0' + hour; timeStr += ':'; } timeStr += min > 9 ? min.toString() : '0' + min; timeStr += ':'; timeStr += sec > 9 ? sec.toString() : '0' + sec; return timeStr; } /** * 根据剩余毫秒数格式化剩余时间 返回 HH:MM:SS * * @param {Number} ms */ public static formatTimeForMillisecond(ms: number) { let second = Math.floor(ms / 1000 % 60); let minute = Math.floor(ms / 1000 / 60 % 60); let hour = Math.floor(ms / 1000 / 60 / 60); let strSecond = second < 10 ? '0' + second : second; let strMinute = minute < 10 ? '0' + minute : minute; let strHour = hour < 10 ? '0' + hour : hour; return `${strSecond}:${strMinute}:${strHour}`; } /** * TODO 需要将pako进行引入,目前已经去除了压缩算法的需要,如需要使用需引入库文件 * 将字符串进行压缩 * @param {String} str */ public static zip(str: string) { const binaryString = pako.gzip(encodeURIComponent(str), { to: 'string' }); // @ts-ignore return this.base64encode(binaryString); } public static rand(arr: any) { let arrClone = this.clone(arr); // 首先从最大的数开始遍历,之后递减 for (let i = arrClone.length - 1; i >= 0; i--) { // 随机索引值randomIndex是从0-arrClone.length中随机抽取的 const randomIndex = Math.floor(Math.random() * (i + 1)); // 下面三句相当于把从数组中随机抽取到的值与当前遍历的值互换位置 const itemIndex = arrClone[randomIndex]; arrClone[randomIndex] = arrClone[i]; arrClone[i] = itemIndex; } // 每一次的遍历都相当于把从数组中随机抽取(不重复)的一个元素放到数组的最后面(索引顺序为:len-1,len-2,len-3......0) return arrClone; } public static randRange(min: any, max: any) { return Math.floor(Math.random() * (max - min + 1)) + min; } public static getPostionNodeInOtherNode(spaceNode: any, targetNode: any) { if (targetNode.parent == null) { return null; } let pos = new Vec3(0, 0, 0); const targetNodeComponent = targetNode.parent.getComponent(UITransform); const spaceNodeComponent = spaceNode.getComponent(UITransform); if (!targetNodeComponent) { targetNode.parent.addComponent(UITransform); } if (!spaceNodeComponent) { spaceNode.addComponent(UITransform); } pos = targetNodeComponent.convertToWorldSpaceAR(targetNode.getPosition()); return spaceNodeComponent ? spaceNodeComponent.convertToNodeSpaceAR(pos) : pos; } public static getPointInNodeFromWorldPoint(worldPoint: Vec3, node: Node) { return node.getComponent(UITransform)?.convertToNodeSpaceAR(worldPoint); } public static validURL(str: string) { var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator return !!pattern.test(str); } public static deepEqual(object1: any, object2: any) { const keys1 = Object.keys(object1); const keys2 = Object.keys(object2); if (keys1.length !== keys2.length) { return false; } for (const key of keys1) { const val1 = object1[key]; const val2 = object2[key]; const areObjects = this.isObject(val1) && this.isObject(val2); if ( areObjects && !this.deepEqual(val1, val2) || !areObjects && val1 !== val2 ) { return false; } } return true; } public static isObject(object: any) { return object != null && typeof object === 'object'; } public static getKeyObject(obj: any, value: any) { let result: string = ""; for (let [key, val] of Object.entries(obj)) { if (value === val) { result = key; break; } } return result; } public static getHighestSiblingIndexOfChild(parentNode: Node): number { let highestSiblingIndex: number = 0; parentNode.children.forEach((child: Node, index: number) => { const siblingIndex = child.getSiblingIndex(); if (highestSiblingIndex < siblingIndex) { highestSiblingIndex = siblingIndex; } }); return highestSiblingIndex; } public static fadeTo(target: Node, duration: number, opacity: number, options?: ITweenOption, callback?: Function): Tween { let uiOpacity = target.getComponent(UIOpacity)!; if (uiOpacity === null) uiOpacity = target.addComponent(UIOpacity); let spine: sp.Skeleton = target.getComponent(sp.Skeleton)!; options = options || {}; if (spine) { if (options.onUpdate) { let _onUpdate = options.onUpdate; options.onUpdate = function () { UmUtil.setAlphaSpine(spine, uiOpacity.opacity); _onUpdate && _onUpdate(); } } else { options.onUpdate = function () { UmUtil.setAlphaSpine(spine, uiOpacity.opacity); } } } let tweenFade = tween(uiOpacity); tweenFade.to(duration, { opacity }, options).start(); (target as any).tweenFade = tweenFade; return tweenFade; } public static fadeIn(target: Node, duration: number, options?: ITweenOption): Tween { return UmUtil.fadeTo(target, duration, 255, options); } public static fadeOut(target: Node, duration: number, options?: ITweenOption): Tween { return UmUtil.fadeTo(target, duration, 0, options); } public static uuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; public static getWindowSize(): cc.Size { return cc.screen.windowSize; } public static getCanvasSize(): cc.Size { const viewSize = cc.screen.windowSize; const designSize = cc.view.getDesignResolutionSize(); let windowWidth = viewSize.width; let windowHeight = viewSize.height; let windowRatio = windowWidth * 1.0 / windowHeight; const designRatio = designSize.width * 1.0 / designSize.height; let height = designSize.height; if (windowRatio <= designRatio) { // let cHeight = Math.floor(designSize.width / windowRatio); height = designSize.width / viewSize.width * viewSize.height; } cc.log("getCanvasSize ==> ", designSize.width, height); return new cc.Size(designSize.width, height); } public static multiplicationExact(numb1: number, numb2: number) { let digit1 = Number(numb1.toString().split('.')[1]?.length); if (Number.isNaN(digit1)) digit1 = 0; let digit2 = Number(numb2.toString().split('.')[1]?.length); if (Number.isNaN(digit2)) digit2 = 0; let maxDigit = digit1 + digit2; for (let i = 0; i < 7; i++) { let factor = Math.pow(10, i); let result = ((numb1 / factor) * (numb2 / factor)) * factor * factor; let digit_check = Number(result.toString().split('.')[1]?.length); if (Number.isNaN(digit_check) || Number(digit_check) <= maxDigit) { // cc.log(" multipl1 ===> digit = ", digit_check, " | pow = 10^", i); return result; } } for (let i = 0; i < 7; i++) { let factor = Math.pow(10, i); let result = ((numb1 / factor) * (numb2 / factor)) * Math.pow(factor, 2); let digit_check = Number(result.toString().split('.')[1]?.length); if (Number.isNaN(digit_check) || Number(digit_check) <= maxDigit) { // cc.log(" multipl2 ===> digit_check = ", digit_check, " | pow = 10^", i); return result; } } // cc.log("RESULT ==> ", result); return numb1 * numb2; } public static setLabelSubStringByMaxWidth(label: cc.Label, maxWidth: number) { label.updateRenderData(); let transform = label.getComponent(UITransform)!; if (transform.width > maxWidth) { while (transform.width > maxWidth) { label.string = label.string.slice(0, -1); label.updateRenderData(); } label.string += "..."; } } public static PlusExact(numb1: number, numb2: number) { let digit1 = Number(numb1.toString().split('.')[1]?.length); if (Number.isNaN(digit1)) digit1 = 0; let digit2 = Number(numb2.toString().split('.')[1]?.length); if (Number.isNaN(digit2)) digit2 = 0; let maxDigit = Math.max(digit1, digit2); for (let i = 0; i < 7; i++) { let factor = Math.pow(10, i); let result = ((numb1 / factor) + (numb2 / factor)) * factor * factor; let digit_check = Number(result.toString().split('.')[1]?.length); if (Number.isNaN(digit_check) || Number(digit_check) <= maxDigit) { // cc.log(" multipl1 ===> digit = ", digit_check, " | pow = 10^", i); return result; } } for (let i = 0; i < 7; i++) { let factor = Math.pow(10, i); let result = ((numb1 / factor) + (numb2 / factor)) * Math.pow(factor, 2); let digit_check = Number(result.toString().split('.')[1]?.length); if (Number.isNaN(digit_check) || Number(digit_check) <= maxDigit) { // cc.log(" multipl2 ===> digit_check = ", digit_check, " | pow = 10^", i); return result; } } // cc.log("RESULT ==> ", result); return numb1 + numb2; } public static setAlphaSpine(spine: sp.Skeleton, alpha: number) { let color: Color = Color.clone(spine.color); color.a = alpha; spine.color = color; } public static moveTo(obj: Node, pointTo: Vec3, duration: number, delayTime?: number, callback?: Function, tag = UmUtil.TWEEN_TAG) { if (!obj) return; tween(obj).delay(delayTime).to(duration, { position: pointTo }).call(() => callback?.()).tag(tag).start(); } public static worldMoveTo(obj: Node, worldPointTo: Vec3, duration: number, delayTime?: number, callback?: Function, tag = UmUtil.TWEEN_TAG) { if (!obj) return; tween(obj).delay(delayTime).to(duration, { worldPosition: worldPointTo }).call(() => callback?.()).tag(tag).start(); } public static scaleTo(obj: Node, scaleTo: Vec3, duration: number, delayTime?: number, callback?: Function, tag = UmUtil.TWEEN_TAG) { if (!obj) return; tween(obj).delay(delayTime).to(duration, { scale: scaleTo }).call(() => callback?.()).tag(tag).start(); } public static delay(obj: Node, delayTime: number, callback: Function, tag = UmUtil.TWEEN_TAG) { tween(obj).delay(delayTime).call(() => { if (obj?.active) { callback?.(); } }).tag(tag).start(); } public static rotateBy(obj: Node, angle: number, duration: number, delayTime?: number, callback?: Function, tag = UmUtil.TWEEN_TAG) { if (!obj) return; tween(obj).delay(delayTime).by(duration, { angle: angle }).call(() => callback?.()).tag(tag).start(); } public static tweenValue(from, to, duration, updateCallback = null, completeCallback = null) { const _target = { value: from }; var tweenValue = tween(_target) .to(duration, { value: to }, { 'onStart': () => { }, 'onUpdate': () => { updateCallback?.(_target.value); }, 'onComplete': () => { completeCallback?.(); } }); tweenValue.start(); return tweenValue; } public static TWEEN_TAG = 2003; public static stopAllTweenTarget(obj: Node) { Tween.stopAllByTarget(obj); } public static stopAllTweenTag(tag: number) { Tween.stopAllByTag(tag); } public static plusTwoVector3(a: Vec3, b: Vec3) { var result = new Vec3(); Vec3.add(result, a, b); return result; } public static subtractTwoVector3(a: Vec3, b: Vec3) { var result = new Vec3(); Vec3.subtract(result, a, b); return result; } public static scaleVector3(a: Vec3, b: number) { return new Vec3(a.x * b, a.y * b, a.z * b); } // public static subtractTwoVector3(a: Vec3, b: Vec3) { // var result = new Vec3(); // Vec3.subtract(result, a, b); // return result; // } }