12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436 |
- import { UITransform, Vec2, Vec3,Node, _decorator, find, director, Color, UIOpacity, tween, Label, Camera, view, Quat, math } from "cc";
- const { ccclass, property } = _decorator;
- import sensitiveArray from '../extend/SensitiveWords';
- import PlatformSystem from "../platform/PlatformSystem";
- import MsgHints from "./MsgHints";
- @ccclass("Utils")
- export class Utils {
- /**
- * 验证字符串是否是空的
- * @s 字符串
- */
- public static isNull(s: string) {
- if(s == ""
- || s == null
- || s == undefined){
- return true;
- }
- if (typeof s === 'string'){
- let re = new RegExp("^[ ]+$");
- return re.test(s);
- }else{
- return false;
- }
- }
- /**
- * 合并多个字典
- * @param args
- */
- public static merge(...args) {
- let mergeFn = <T, U> (arg1: T, arg2: U) : (T & U) =>{
- let res = {} as (T & U);
- res = Object.assign(arg1, arg2);
- return res;
- };
- let nDict = {};
- args.forEach(obj => {
- nDict = mergeFn(nDict,obj);
- });
- return nDict;
- }
-
- /**
- * 排除合并对象,但排除'reward'和'dec'属性(如果e中已存在)
- * mergeWithExceptions(e, pData, ['reward', 'dec']);
- * @param target
- * @param source
- * @param exclude
- * @returns
- */
- public static mergeWithExceptions(target: any, source: any, exclude: string[]) {
- Object.keys(source).forEach(key => {
- if (!exclude.includes(key) || !(key in target)) {
- target[key] = source[key];
- }
- });
- return target;
- }
- /**
- * 深度拷贝
- * @param {any} sObj 拷贝的对象
- * @returns
- */
- public static clone(sObj: any) {
- if (sObj === null || typeof sObj !== "object") {
- return sObj;
- }
- let s: { [key: string]: any } = {};
- if (sObj.constructor === Array) {
- s = [];
- }
- for (let i in sObj) {
- if (sObj.hasOwnProperty(i)) {
- s[i] = this.clone(sObj[i]);
- }
- }
- return s;
- }
- /**
- * 转换数字
- */
- public static formatUnits(num: number): string {
- if (num >= 1e8) {
- return (num / 1e8).toFixed(1) + '亿';
- } else if (num >= 1e7) {
- return (num / 1e7).toFixed(1) + '千万';
- } else if (num >= 1e6) {
- return (num / 1e6).toFixed(1) + '百万';
- } else if (num >= 1e4) {
- return (num / 1e4).toFixed(1) + '万';
- } else if (num >= 1000) {
- return (num / 1000).toFixed(1) + 'k';
- } else {
- return num.toString();
- }
- }
- /**
- * 将object转化为数组
- * @param { any} srcObj
- * @returns
- */
- public static objectToArray(srcObj: { [key: string]: any }) {
- let resultArr: any[] = [];
- // to array
- for (let key in srcObj) {
- if (!srcObj.hasOwnProperty(key)) {
- continue;
- }
- resultArr.push(srcObj[key]);
- }
- return resultArr;
- }
- /**
- * !#zh 将数组转化为object。
- */
- /**
- * 将数组转化为object。
- * @param { any} srcObj
- * @param { string} objectKey
- * @returns
- */
- public static arrayToObject(srcObj: any, objectKey: string) {
- let resultObj: { [key: string]: any } = {};
- // to object
- for (var key in srcObj) {
- if (!srcObj.hasOwnProperty(key) || !srcObj[key][objectKey]) {
- continue;
- }
- resultObj[srcObj[key][objectKey]] = srcObj[key];
- }
- return resultObj;
- }
- /**
- * 根据权重,计算随机内容
- * @param {arrany} weightArr
- * @param {number} totalWeight 权重
- * @returns
- */
- public static getWeightRandIndex(weightArr: [], totalWeight: number) {
- let randWeight: number = Math.floor(Math.random() * totalWeight);
- let sum: number = 0;
- for (var weightIndex: number = 0; 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) {
- let array: any[] = [];
- let intRd: number = 0;
- let count: number = 0;
- while (count < m) {
- if (count >= n + 1) {
- break;
- }
- intRd = this.getRandomInt(0, n);
- var flag = 0;
- for (var i = 0; i < count; i++) {
- if (array[i] === intRd) {
- flag = 1;
- break;
- }
- }
- if (flag === 0) {
- array[count] = intRd;
- count++;
- }
- }
- return array;
- }
- /**
- * 获取随机整数
- * @param {Number} min 最小值
- * @param {Number} max 最大值
- * @returns
- */
- public static getRandomInt(min: number, max: number) {
- let r: number = Math.random();
- let rr: number = r * (max - min + 1) + min;
- return Math.floor(rr);
- }
- /**
- * 获取字符串长度
- * @param {string} render
- * @returns
- */
- public static getStringLength(render: string) {
- let strArr: string = render;
- let len: number = 0;
- for (let i: number = 0, n = strArr.length; i < n; i++) {
- let val: number = strArr.charCodeAt(i);
- if (val <= 255) {
- len = len + 1;
- } else {
- len = len + 2;
- }
- }
- return Math.ceil(len / 2);
- }
- /**
- * 要从一个数组模型中随机取出 n 个元素
- * @param arr
- * @param n
- * @returns 返回一个新的数组
- */
- public static getRandomElements<T>(arr: T[], n: number): T[] {
- if (n <= 0) return []; // 如果 n 小于等于 0,返回空数组
- //复制数组以避免修改原数组
- const copy = [...arr];
- //Fisher-Yates 洗牌算法
- for (let i = copy.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [copy[i], copy[j]] = [copy[j], copy[i]];
- }
- //如果 n 超过数组长度,返回乱序后的整个数组
- if (n >= copy.length) {
- return copy;
- }
- //返回前 n 个元素
- return copy.slice(0, n);
- }
- /**
- * 判断传入的参数是否为空的Object。数组或undefined会返回false
- * @param obj
- */
- public static isEmptyObject(obj: any) {
- let result: boolean = true;
- if (obj && obj.constructor === Object) {
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- result = false;
- break;
- }
- }
- } else {
- result = false;
- }
- return result;
- }
- /**
- * 判断是否是新的一天
- * @param {Object|Number} dateValue 时间对象 todo MessageCenter 与 pve 相关的时间存储建议改为 Date 类型
- * @returns {boolean}
- */
- public static isNewDay(dateValue: any) {
- // todo:是否需要判断时区?
- var oldDate: any = new Date(dateValue);
- var curDate: any = new Date();
- var oldYear = oldDate.getYear();
- var oldMonth = oldDate.getMonth();
- var oldDay = oldDate.getDate();
- var curYear = curDate.getYear();
- var curMonth = curDate.getMonth();
- var curDay = curDate.getDate();
- if (curYear > oldYear) {
- return true;
- } else {
- if (curMonth > oldMonth) {
- return true;
- } else {
- if (curDay > oldDay) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * 获取对象属性数量
- * @param {object}o 对象
- * @returns
- */
- public static getPropertyCount(o: Object) {
- var n, count = 0;
- for (n in o) {
- if (o.hasOwnProperty(n)) {
- count++;
- }
- }
- return count;
- }
- /**
- * 返回一个差异化数组(将array中diff里的值去掉)
- * @param array
- * @param diff
- */
- public static difference(array: [], diff: any) {
- let result: any[] = [];
- if (array.constructor !== Array || diff.constructor !== Array) {
- return result;
- }
- let length = array.length;
- for (let i: number = 0; i < length; i++) {
- if (diff.indexOf(array[i]) === -1) {
- result.push(array[i]);
- }
- }
- return result;
- }
- public static _stringToArray(string: string) {
- // 用于判断emoji的正则们
- var rsAstralRange = '\\ud800-\\udfff';
- var rsZWJ = '\\u200d';
- var rsVarRange = '\\ufe0e\\ufe0f';
- var rsComboMarksRange = '\\u0300-\\u036f';
- var reComboHalfMarksRange = '\\ufe20-\\ufe2f';
- var rsComboSymbolsRange = '\\u20d0-\\u20ff';
- var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange;
- var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
- var rsFitz = '\\ud83c[\\udffb-\\udfff]';
- var rsOptVar = '[' + rsVarRange + ']?';
- var rsCombo = '[' + rsComboRange + ']';
- var rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')';
- var reOptMod = rsModifier + '?';
- var rsAstral = '[' + rsAstralRange + ']';
- var rsNonAstral = '[^' + rsAstralRange + ']';
- var rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}';
- var rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]';
- var rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*';
- var rsSeq = rsOptVar + reOptMod + rsOptJoin;
- var rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
- var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
- var hasUnicode = function (val: any) {
- return reHasUnicode.test(val);
- };
- var unicodeToArray = function (val: any) {
- return val.match(reUnicode) || [];
- };
- var asciiToArray = function (val: any) {
- return val.split('');
- };
- return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
- }
- // 模拟传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: any, end: any) {
- var startTime = new Date(start);
- var endTime = new Date(end);
- var result = false;
- if (startTime.getDate() + '' !== 'NaN' && endTime.getDate() + '' !== 'NaN') {
- var curDate = new Date();
- result = curDate < endTime && curDate > startTime;
- }
- return result;
- }
- /**
- * 返回相隔天数
- * @param start
- * @param end
- * @returns
- */
- public static getDeltaDays(start: any, end: any) {
- start = new Date(start);
- end = new Date(end);
- let startYear: number = start.getFullYear();
- let startMonth: number = start.getMonth() + 1;
- let startDate: number = start.getDate();
- let endYear: number = end.getFullYear();
- let endMonth: number = end.getMonth() + 1;
- let endDate: number = end.getDate();
- start = new Date(startYear + '/' + startMonth + '/' + startDate + ' GMT+0800').getTime();
- end = new Date(endYear + '/' + endMonth + '/' + endDate + ' GMT+0800').getTime();
- let deltaTime = end - start;
- return Math.floor(deltaTime / (24 * 60 * 60 * 1000));
- }
- /**
- * 获取数组最小值
- * @param array 数组
- * @returns
- */
- public static getMin(array: number[]) {
- let result: number = null!;
- if (array.constructor === Array) {
- let 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;
- }
- /**
- * 格式化两位小数点
- * @param time
- * @returns
- */
- public static formatTwoDigits(time: number) {
- //@ts-ignore
- return (Array(2).join(0) + time).slice(-2);
- }
- /**
- * 根据格式返回时间
- * @param date 时间
- * @param fmt 格式
- * @returns
- */
- public static formatDate(date: Date, fmt: string) {
- let o: any = {
- "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 (let 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() {
- let date: Date = new Date();
- return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
- }
- /**
- * 格式化名字,XXX...
- * @param {string} name 需要格式化的字符串
- * @param {number}limit
- * @returns {string} 返回格式化后的字符串XXX...
- */
- public static formatName(name: string, limit: number) {
- limit = limit || 6;
- var nameArray = this._stringToArray(name);
- var str = '';
- var length = nameArray.length;
- if (length > limit) {
- for (var i = 0; i < limit; i++) {
- str += nameArray[i];
- }
- str += '...';
- } else {
- str = name;
- }
- return str;
- }
- /**
- * 格式化钱数,超过10000 转换位 10K 10000K 转换为 10M
- * @param {number}money 需要被格式化的数值
- * @returns {string}返回 被格式化的数值
- */
- public static formatMoney(money: number) {
- let arrUnit: string[] = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'B', 'N', 'D'];
- let strValue: string = '';
- for (let idx: number = 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;
- }
- /**
- * 开始展示文字
- * @param lable 文本
- * @param words 播放的文字
- * @param cb 播放完成回调
- * @param cbbS 回调延迟
- * @param delay 延迟逐字播放
- * @param s 一个字的播放速度/秒
- */
- public static verbatim(lable: Label,words: string, cb?: Function,cbbS:number = 0,delay: number = 0,s: number = 0.1,){
- if (!words.hasOwnProperty('length'))return;
- lable.node.active = true;
- lable.unscheduleAllCallbacks();
- let f = function(){
- let arr = words.replace(/ /g,"").split('');
- var step = 0;
- var allWords: string = "";
- let fun = ()=> {
- allWords += arr[step];
- lable.string = allWords;
- if(++step >= arr.length) {
- lable.unschedule(fun);
- let cbFun = ()=>{cb?.()};
- cbbS > 0 ? (lable.scheduleOnce(cbFun, cbbS)) : cbFun();
- }
- };
- lable.schedule(fun,s,Number.MAX_SAFE_INTEGER);
- };
- delay > 0 ? (lable.scheduleOnce(f.bind(this),delay)) : f();
- }
- /**
- * 格式化数值
- * @param {number}value 需要被格式化的数值
- * @returns {string}返回 被格式化的数值
- */
- public static formatValue(value: number) {
- let arrUnit: string[] = [];
- let strValue: string = '';
- for (let i = 0; i < 26; i++) {
- arrUnit.push(String.fromCharCode(97 + i));
- }
- for (let idx: number = 0; idx < arrUnit.length; idx++) {
- if (value >= 10000) {
- value /= 1000;
- } else {
- strValue = Math.floor(value) + arrUnit[idx];
- break;
- }
- }
- return strValue;
- }
- /**
- * 根据剩余秒数格式化剩余时间 返回 HH:MM:SS
- * @param {Number} leftSec
- */
- public static formatTimeForSecond(leftSec: number, withoutSeconds: boolean = false) {
- let timeStr: string = '';
- let sec: number = leftSec % 60;
- let leftMin: number = Math.floor(leftSec / 60);
- leftMin = leftMin < 0 ? 0 : leftMin;
- let hour: number = Math.floor(leftMin / 60);
- let min: number = leftMin % 60;
- if (hour > 0) {
- timeStr += hour > 9 ? hour.toString() : '0' + hour;
- timeStr += ':';
- } else {
- timeStr += '00:';
- }
- timeStr += min > 9 ? min.toString() : '0' + min;
- if (!withoutSeconds) {
- timeStr += ':';
- timeStr += sec > 9 ? sec.toString() : '0' + sec;
- }
- return timeStr;
- }
- /**
- * 计算 3D 空间中两点之间的欧几里得距离
- * @param a 第一个点
- * @param b 第二个点
- * @returns 两点之间的距离
- */
- public static distance(a: Vec3, b: Vec3): number {
- const dx = b.x - a.x;
- const dy = b.y - a.y;
- const dz = b.z - a.z;
- return Math.sqrt(dx * dx + dy * dy + dz * dz);
- }
- /**
- * 根据剩余毫秒数格式化剩余时间 返回 HH:MM:SS
- *
- * @param {Number} ms
- */
- public static formatTimeForMillisecond(ms: number): Object {
- let second: number = Math.floor(ms / 1000 % 60);
- let minute: number = Math.floor(ms / 1000 / 60 % 60);
- let hour: number = Math.floor(ms / 1000 / 60 / 60);
- return { 'hour': hour, 'minute': minute, 'second': second };
- }
- /**
- * 格式化时间戳字符串
- * @param timestamp 1740006560000
- * @returns 输出2025-02-20 05:09:20
- */
- public static formatTimestamp(timestamp: number): string {
- const date = new Date(timestamp);
- //获取年月日时分秒
- const year = date.getFullYear();
- //月份从 0 开始,需要 +1
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
- const seconds = String(date.getSeconds()).padStart(2, '0');
- //拼接成 YYYY-MM-DD HH:MM:SS 格式
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- }
- /**
- * 随机乱序数组
- * @param array
- * @returns
- */
- public static randomArray(array) {
- // 使用 Fisher-Yates Shuffle 算法
- for (let i = array.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- // 交换元素
- [array[i], array[j]] = [array[j], array[i]];
- }
- return array;
- }
- /**
- * 获得开始和结束两者之间相隔分钟数
- *
- * @static
- * @param {number} start
- * @param {number} end
- * @memberof utils
- */
- public static getOffsetMimutes(start: number, end: number) {
- let offSetTime: number = end - start;
- let minute: number = Math.floor((offSetTime % (1000 * 60 * 60)) / (1000 * 60));
- return minute;
- }
- /**
- * 获取随机小数
- * @param {Number} min 最小值
- * @param {Number} max 最大值
- * @returns
- */
- public static getRandomFloat(min: number, max: number) {
- return Math.random() * (max - min) + min;
- }
-
- /**
- * 返回指定小数位的数值
- * @param {number} num
- * @param {number} idx
- */
- public static formatNumToFixed(num: number, idx: number = 0) {
- return Number(num.toFixed(idx));
- }
- /**
- * 用于数值到达另外一个目标数值之间进行平滑过渡运动效果
- * @param {number} targetValue 目标数值
- * @param {number} curValue 当前数值
- * @param {number} ratio 过渡比率
- * @returns
- */
- public static lerp(targetValue: number, curValue: number, ratio: number = 0.25) {
- let v: number = curValue;
- if (targetValue > curValue) {
- v = curValue + (targetValue - curValue) * ratio;
- } else if (targetValue < curValue) {
- v = curValue - (curValue - targetValue) * ratio;
- }
- return v;
- }
- /**
- * 数据解密
- * @param {String} str
- */
- public static decrypt(b64Data: string) {
- if(b64Data == null || b64Data == undefined){
- return "";
- }
- let n: number = 6;
- if (b64Data.length % 2 === 0) {
- n = 7;
- }
- let decodeData = '';
- for (var idx = 0; idx < b64Data.length - n; idx += 2) {
- decodeData += b64Data[idx + 1];
- decodeData += b64Data[idx];
- }
- decodeData += b64Data.slice(b64Data.length - n + 1);
- decodeData = this._base64Decode(decodeData);
- return decodeData;
- }
- /**
- * 数据加密
- * @param {String} str
- */
- public static encrypt(str: string) {
- if(str == null || str == undefined){
- return "";
- }
- let b64Data = this._base64encode(str);
- let n: number = 6;
- if (b64Data.length % 2 === 0) {
- n = 7;
- }
- let encodeData: string = '';
- for (let idx = 0; idx < (b64Data.length - n + 1) / 2; idx++) {
- encodeData += b64Data[2 * idx + 1];
- encodeData += b64Data[2 * idx];
- }
- encodeData += b64Data.slice(b64Data.length - n + 1);
- return encodeData;
- }
- //public method for encoding
- /**
- * base64加密
- * @param {string}input
- * @returns
- */
- private static _base64encode(input: string) {
- let keyStr: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- let output: string = "", chr1: number, chr2: number, chr3: number, enc1: number, enc2: number, enc3: number, enc4: number, i: number = 0;
- input = this._utf8Encode(input);
- while (i < input.length) {
- chr1 = input.charCodeAt(i++);
- chr2 = input.charCodeAt(i++);
- chr3 = input.charCodeAt(i++);
- enc1 = chr1 >> 2;
- enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
- enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
- enc4 = chr3 & 63;
- if (isNaN(chr2)) {
- enc3 = enc4 = 64;
- } else if (isNaN(chr3)) {
- enc4 = 64;
- }
- output = output +
- keyStr.charAt(enc1) + keyStr.charAt(enc2) +
- keyStr.charAt(enc3) + keyStr.charAt(enc4);
- }
- return output;
- }
- /**
- * utf-8 加密
- * @param string
- * @returns
- */
- private static _utf8Encode(string: string) {
- string = string.replace(/\r\n/g, "\n");
- let utftext: string = "";
- for (let n: number = 0; n < string.length; n++) {
- let c: number = string.charCodeAt(n);
- if (c < 128) {
- utftext += String.fromCharCode(c);
- } else if ((c > 127) && (c < 2048)) {
- utftext += String.fromCharCode((c >> 6) | 192);
- utftext += String.fromCharCode((c & 63) | 128);
- } else {
- utftext += String.fromCharCode((c >> 12) | 224);
- utftext += String.fromCharCode(((c >> 6) & 63) | 128);
- utftext += String.fromCharCode((c & 63) | 128);
- }
- }
- return utftext;
- }
- /**
- * utf-8解密
- * @param utftext
- * @returns
- */
- private static _utf8Decode(utftext: string) {
- let string = "";
- let i: number = 0;
- let c: number = 0;
- let c1: number = 0;
- let c2: number = 0;
- let c3: number = 0;
- while (i < utftext.length) {
- c = utftext.charCodeAt(i);
- if (c < 128) {
- string += String.fromCharCode(c);
- i++;
- } else if ((c > 191) && (c < 224)) {
- c2 = utftext.charCodeAt(i + 1);
- string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
- i += 2;
- } else {
- c2 = utftext.charCodeAt(i + 1);
- c3 = utftext.charCodeAt(i + 2);
- string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
- i += 3;
- }
- }
- return string;
- }
- /**
- * base64解密
- * @param {string}input 解密字符串
- * @returns
- */
- private static _base64Decode(input: string) {
- let keyStr: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- let output: string = "";
- let chr1: number;
- let chr2: number;
- let chr3: number;
- let enc1: number;
- let enc2: number;
- let enc3: number;
- let enc4: number;
- let i: number = 0;
- input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
- while (i < input.length) {
- enc1 = keyStr.indexOf(input.charAt(i++));
- enc2 = keyStr.indexOf(input.charAt(i++));
- enc3 = keyStr.indexOf(input.charAt(i++));
- enc4 = keyStr.indexOf(input.charAt(i++));
- chr1 = (enc1 << 2) | (enc2 >> 4);
- chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
- chr3 = ((enc3 & 3) << 6) | enc4;
- output = output + String.fromCharCode(chr1);
- if (enc3 != 64) {
- output = output + String.fromCharCode(chr2);
- }
- if (enc4 != 64) {
- output = output + String.fromCharCode(chr3);
- }
- }
- output = this._utf8Decode(output);
- return output;
- }
-
- /**
- * 页面渐隐渐显动画
- * @param n 节点
- * @param isApper 是否是出现
- * @param restore 是否恢复255显示的状态
- * @param cb 执行完回调
- */
- public static pageAnim(n: Node,isApper: boolean = true,cb?:Function){
- let uiop:UIOpacity = n.getComponent(UIOpacity);
- if(!n || !uiop){
- cb?.();
- }else{
- uiop.opacity = isApper ? 25 : 255;
- let toOpacity: number = isApper ? 255 : 25;
- tween(uiop)
- .to(0.4,{opacity:toOpacity})
- .call(function(){
- cb?.();
- }.bind(this))
- .start();
- }
- }
-
- /**
- * 将数组(array)拆分成多个 size 长度的区块,并将这些区块组成一个新数组
- * @param {Array}array
- * @param {number}size
- * @returns
- */
- public static chunk(array: any[], size: number) {
- var length = array === null ? 0 : array.length;
- if (!length || size < 1) {
- return [];
- }
- let result = [];
- while (array.length > size) {
- result.push(array.slice(0, size));
- array = array.slice(size);
- }
- result.push(array);
- return result;
- }
-
- /**
- * 一个字符串首字母大写
- */
- public static upperCase(str){
- if(this.isNull(str))return "";
- return str.replace(/^\w/, (c) => c.toUpperCase());
- }
- /**
- * 查询一个节点下的子节点
- */
- public static findName(root: Node, name: string) : Node{
- let child: Node;
- if(name.indexOf("/") != -1) {
- child = find(name, root);
- }else {
- if(!root) {root = director.getScene();}
- if (root.name === name) {
- child = root;
- } else {
- child = this.findChild(name, root);
- }
- }
- if(child) {
- return child;
- } else {
- console.log("没有找到指定的Node, node name ==", name);
- return null;
- }
- }
- private static findChild(name: string, parent: Node): Node {
- let child: Node = parent.getChildByName(name);
- if (child) {
- return child;
- } else {
- let children: Node[] = parent.children;
- for (let i = 0; i < children.length; i++) {
- child = this.findChild(name, children[i]);
- if (child) {
- return child;
- }
- }
- }
- return null;
- }
- /**
- * 处理万为单位
- * @param num 数值
- * @param point 保留小数点
- * @param s 是否去掉无用的0
- * @returns
- */
- public static numUnit(num: number,point: number = 0,f: boolean = true): string {
- let n: number = num;
- let unit: number = 10000;
- if (n > unit) {
- if(point == 0){
- n = Math.ceil(n / unit);
- return n.toString() + "万";
- }else{
- let s: string = (n / unit).toFixed(point);
- if(f){
- return this.removeZeros(s) + "万";
- }else{
- return s + "万";
- }
- }
- }else{
- return Math.ceil(num).toString();
- }
- }
- /**
- * 格式化数字:
- * * 如果小数部分全是 0(如 38.0 或 38.00),去掉小数部分,返回整数。
- * 如果是小数(如 38.1 或 38.01),保留两位小数。
- * @param num 输入的数字
- * @returns 格式化后的数字
- */
- public static formatNumber(num: number): number | string {
- // 如果 num 是 undefined 或 null,返回 0 或空字符串
- if (num == undefined || num == null) return 0;
- //将数字转换为字符串
- const numStr = num.toString();
- //判断小数部分是否全是 0 如果小数部分全是 0,去掉小数部分并返回整数
- if (numStr.includes('.') && /\.0+$/.test(numStr)) {
- return parseInt(numStr, 10);
- } else {
- //否则保留两位小数
- const fixedNum = num.toFixed(2);
- //如果小数部分全是 0,去掉小数部分
- if(fixedNum.endsWith(".00")) {
- return parseInt(fixedNum, 10);
- }
- return parseFloat(fixedNum);
- }
- }
- /**
- * 去掉小数点后无用的0
- * @param numberString 字符串呢
- * @returns
- */
- public static removeZeros(numberString: string): string {
- const trimmedString = numberString.trim(); // 去除首尾空格
- const decimalIndex = trimmedString.indexOf('.');
- if (decimalIndex !== -1) {
- let endIndex = trimmedString.length - 1;
- while (trimmedString[endIndex] === '0') {
- endIndex--;
- }
- if (trimmedString[endIndex] === '.') {
- endIndex--; // 如果小数点后面全是零,也去掉小数点
- }
- return trimmedString.slice(0, endIndex + 1);
- }
- return trimmedString;
- }
- /**
- * 数组移除某一个元素
- */
- public static remove(arr,param){
- let index = arr.indexOf(param)
- if (index > -1) {
- arr.splice(index, 1)
- }
- }
- /**
- * 16进制的颜色
- * @param hexColor
- * @returns
- */
- public static hexColor(hexColor) {
- const hex = hexColor.replace(/^#?/, "0x");
- const c = parseInt(hex);
- const r = c >> 16;
- const g = (65280 & c) >> 8;
- const b = 255 & c;
- return new Color(r, g, b, 255);
- };
- /**
- * 检查是否有铭感词
- * @param str 文字
- * @returns 返回是否有铭感词
- */
- public static filtion(str): boolean{
- return sensitiveArray.some(word => {
- //忽略大小写匹配
- const lowerCaseStr = str.toLowerCase();
- const lowerCaseWord = word.toLowerCase();
- return lowerCaseStr.includes(lowerCaseWord);
- });
- }
- /**
- * 计算两点间的距离
- */
- public static pDistance(localPos: Vec3,tarPos: Vec3): number {
- let dx = localPos.x - tarPos.x;
- let dy = localPos.y - tarPos.y;
- let dis = Math.sqrt(dx * dx + dy * dy);
- return dis;
- }
-
- /**
- * 计算两点之间的绝对距离
- */
- public static pAbsDistance(a: Vec3,b: Vec3): number {
- let p: number = Math.abs(a.x - b.x);
- let k: number = Math.abs(a.y - b.y);
- return p + k;
- }
- /**
- * 角度转向量
- * @param angle
- * @returns
- */
- public static angle_to_vector (angle: number): Vec2 {
- // tan = sin / cos 将传入的角度转为弧度
- let radian = this.angle_to_radian(angle);
- // 算出cos,sin和tan
- let cos = Math.cos(radian);// 邻边 / 斜边
- let sin = Math.sin(radian);// 对边 / 斜边
- let tan = sin / cos;// 对边 / 邻边
- //结合在一起并归一化
- let vec = new Vec2(cos, sin).normalize();
- //返回向量
- return(vec);
- }
- /**
- * 向量转角度
- * @param vector
- * @returns
- */
- public static vector_to_angle (vector: Vec2): number {
- //将传入的向量归一化
- let dir = vector.normalize();
- //计算出目标角度的弧度
- let radian = dir.signAngle(new Vec2(1, 0));
- //把弧度计算成角度
- let angle = -this.radian_to_angle(radian);
- //返回角度
- return(angle);
- }
- /**
- * 角度转弧度
- * @param angle
- * @returns
- */
- public static angle_to_radian (angle: number): number {
- //角度转弧度公式 π / 180 * 角度 计算出弧度
- let radian = Math.PI / 180 * angle;
- //返回弧度
- return(radian);
- }
- /**
- * 弧度转角度
- * @param radian
- * @returns
- */
- public static radian_to_angle (radian: number): number {
- //弧度转角度公式 180 / π * 弧度 计算出角度
- let angle = 180 / Math.PI * radian;
- //返回角度
- return(angle);
- }
- /**
- * 计算弧度
- * @param start
- * @param end
- */
- public static getAngle(start: Vec3, end: Vec3) {
- //两点的x、y值
- var x = end.x - start.x;
- var y = end.y - start.y;
- var hypotenuse = Math.sqrt(x * x + y * y);
- //斜边长度
- var cos = x / hypotenuse;
- var radian = Math.acos(cos);
- //求出弧度
- var angle = 180 / (Math.PI / radian);
- //用弧度算出角度
- if (y < 0) {
- angle = 0 - angle;
- }else if (y == 0 && x < 0) {
- angle = 180;
- }
- return angle;
- }
- /**
- * 扣血转化成字符串
- * @param number
- * @returns
- */
- public static numberToString(value: number | string) {
- console.log("当前的值: " + value);
- const num = Number(value);
- if (isNaN(num)) return "0";
- //特殊处理:小于1百万显示完整数字
- if(num < 100000) {
- return Math.floor(num).toString();
- }
- //使用标准格式化中文单位
- return this.formatBigNumber(num, ["", "K", "M", "B"],1000);
- }
- /**
- * 基本用法(自动中文单位)
- console.log(formatChineseBigNumber(998989)); // "998989"
- console.log(formatChineseBigNumber(24326755)); // "2432.67万"
- console.log(formatChineseBigNumber(958753111)); // "9.58亿"
- console.log(formatChineseBigNumber(9587531115987));// "9.58万亿"
- //高级用法(自定义单位)
- console.log(formatBigNumber(1500, ["", "K", "M"], 1000)); // "1.5K"
- console.log(formatBigNumber(2500000, ["", "K", "M"], 1000)); // "2.5M"
- * 格式化大数字带单位
- * @param value 要格式化的数字或字符串
- * @param customUnits 可选的自定义单位数组(从大到小排序)
- * @param customK 可选的进制基数(默认10000)
- * @returns 格式化后的字符串
- */
- public static formatBigNumber(
- value: number | string,
- customUnits?: string[],
- customK: number = 10000
- ): string {
- const num = Number(value);
- if (isNaN(num)) return "0";
- const defaultUnits = ["", "万", "亿", "万亿"];
- const units = customUnits || defaultUnits;
- const k = customK;
- //小于1万的直接返回原数字
- if(num < k) {
- return num % 1 === 0 ? num.toString() : num.toFixed(2);
- }
- //计算单位
- const i = Math.floor(Math.log(num) / Math.log(k));
- const unitIndex = Math.min(i, units.length - 1);
- const divided = num / Math.pow(k, unitIndex);
- // 智能确定小数位数
- let decimalPlaces = 2;
- if (divided >= 1000) decimalPlaces = 0;
- else if (divided >= 100) decimalPlaces = 1;
- // 格式化并去除无效的.00
- let formatted = divided.toFixed(decimalPlaces);
- if(formatted.endsWith(".00")) {
- formatted = formatted.slice(0, -3);
- }else if (formatted.endsWith("0") && decimalPlaces > 0) {
- //处理类似1032.50万 -> 1032.5万的情况
- formatted = formatted.replace(/0+$/, "").replace(/\.$/, "");
- }
- return formatted + units[unitIndex];
- }
- /**
- * 将某个节点下的坐标转移到另外一个节点
- * @param fromNode 坐标所在的节点
- * @param toNode 目标节点
- * @returns 转换后的坐标值
- */
- public static convertPosition(fromNode: Node, toNode: Node): Vec3 {
- let pos: Vec3 = fromNode.position.clone();
- // 将 pos 转为世界坐标系下的坐标
- const worldPos: Vec3 = fromNode.parent.getComponent(UITransform).convertToWorldSpaceAR(pos);
- // 将世界坐标系下的坐标转为目标节点的局部坐标系下的坐标
- const localPos: Vec3 = toNode.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
- return localPos;
- }
- /**
- * 以敌人的中心点 攻击范围为半径 产生随机坐标
- * @param center 中心点
- * @param radius 半径
- * @returns
- */
- public static randomPointGenerator(center: Vec3,radius: number){
- //随机角度
- let angle = Math.random() * Math.PI * 2;
- //随机距离
- let distance = Math.sqrt(Math.random()) * radius;
- //根据极坐标转换成笛卡尔坐标
- const x = center.x + distance * Math.cos(angle);
- const y = center.y + distance * Math.sin(angle);
- return new Vec3(x,y,1);
- }
- /**
- * 将某个节点上的坐标转移到另外一个节点
- * @param fromNode 坐标所在的节点
- * @param toNode 目标节点
- * @returns 转换后的坐标值
- */
- public static convertPositionPos(fromNode: Node,pos: Vec3, toNode: Node): Vec3 {
- let nPos: Vec3 = pos.clone();
- // 将 pos 转为世界坐标系下的坐标
- const worldPos: Vec3 = fromNode.parent.getComponent(UITransform).convertToWorldSpaceAR(nPos);
- // 将世界坐标系下的坐标转为目标节点的局部坐标系下的坐标
- const localPos: Vec3 = toNode.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
- return localPos;
- }
- /**
- * 获取屏幕中心对应的3D世界坐标
- * @param camera 摄像机
- * @param groundY 地面高度
- * @returns
- */
- public static getScreenCenterWorldPos(camera: Camera,groundY:number = 0): Vec3 {
- if (!camera) return Vec3.ZERO;
- //获取屏幕中心坐标
- const screenSize = view.getVisibleSize();
- const centerX = screenSize.width / 2;
- const centerY = screenSize.height / 2;
- //生成从屏幕中心发出的射线
- const ray = camera.screenPointToRay(centerX, centerY);
- //计算射线与特定平面的交点(假设地面Y=0)
- const distance = (groundY - ray.o.y) / ray.d.y;
- // 4. 返回交点坐标
- return new Vec3(
- ray.o.x + ray.d.x * distance,
- ray.o.y + ray.d.y * distance,
- ray.o.z + ray.d.z * distance
- );
- }
- /**
- * 让相机平滑看向目标节点
- * @param selfNode 设置方向的节点
- * @param targetNode 目标节点
- * @param duration 平滑过渡时间(秒),默认0.3秒
- */
- public static lookAtNode(selfNode: Node, targetNode: Node, duration: number = 0.3): void {
- if(!selfNode || !targetNode) return;
- //计算水平方向(忽略Y轴差异)
- const targetPos = targetNode.worldPosition;
- const selfPos = selfNode.worldPosition;
- const direction = new Vec3();
- Vec3.subtract(direction, targetPos, selfPos);
- //关键修复:将Y轴归零计算水平方向
- const horizontalDir = new Vec3(direction.x, 0, direction.z).normalize();
- //计算垂直角度(仅Y轴差异)
- const distance = Vec3.distance(targetPos, selfPos);
- const heightDiff = targetPos.y - selfPos.y;
- const verticalAngle = math.toRadian(math.clamp(
- Math.atan2(heightDiff, Math.sqrt(direction.x * direction.x + direction.z * direction.z)) * 180 / Math.PI,
- -89, 89 // 限制在±89度内防止万向节锁
- ));
- //构建最终旋转(先水平后垂直)
- const targetRotation = new Quat();
- Quat.fromViewUp(targetRotation, horizontalDir, Vec3.UP);
- //添加垂直旋转
- const verticalRot = new Quat();
- Quat.fromEuler(verticalRot, verticalAngle, 0, 0);
- Quat.multiply(targetRotation, targetRotation, verticalRot);
- //应用旋转
- if(duration <= 0) {
- selfNode.setRotation(targetRotation);
- }else{
- const startRotation = selfNode.rotation.clone();
- tween(selfNode)
- .to(duration, { rotation: targetRotation }, {
- onUpdate: (_, ratio: number) => {
- const currentRot = new Quat();
- Quat.slerp(currentRot, startRotation, targetRotation, ratio);
- selfNode.setRotation(currentRot);
- }
- })
- .start();
- }
- }
-
- /**
- * 根据A,B两个坐标点 和抛物线的弧度 来计算中心点坐标
- */
- public static calculateParabolaCenter(start: Vec3, end: Vec3){
- // 计算两点之间的水平距离
- const deltaX = end.x - start.x;
- // 将控制点的 x 坐标设置为两点的中点
- const controlX = (start.x + end.x) / 2;
- // 计算抛物线的最高点,使其位于两点之间的中间位置 可以根据需要调整最高点的位置
- const highestY = Math.max(start.y, end.y) + Math.abs(deltaX) / 4;
- // 计算控制点的 y 坐标
- const controlY = highestY;
- //返回抛物线的中心坐标点
- return new Vec3(controlX, controlY);
- }
- /**
- * 在UI坐标系内生成安全的随机点
- * @param uiParent 父节点(UITransform)
- * @param center 中心点(Vec3) - 基于父节点的本地坐标
- * @param radius 半径(Number) - 单位为像素
- */
- public static randomUIPointGenerator(uiParent: Node, center: Vec3, radius: number): Vec3 {
- // 将像素半径转换为世界坐标比例
- const uiSize = uiParent.getComponent(UITransform).contentSize;
- const maxRadius = Math.min(
- uiSize.width / 2 - Math.abs(center.x),
- uiSize.height / 2 - Math.abs(center.y)
- );
- const safeRadius = Math.min(radius, maxRadius);
- //生成随机角度和距离(距离使用平方根使分布均匀)
- const angle = Math.random() * Math.PI * 2;
- const distance = Math.sqrt(Math.random()) * safeRadius;
- //转换为本地坐标
- return new Vec3(
- center.x + distance * Math.cos(angle),
- center.y + distance * Math.sin(angle),
- 0 // UI节点z轴设为0
- );
- }
-
- /**
- * 生成一个随机颜色值的函数
- */
- public static getRandomColor() {
- let r = Math.floor(Math.random() * 256);
- let g = Math.floor(Math.random() * 256);
- let b = Math.floor(Math.random() * 256);
- return new Color(r, g, b);
- }
- /**
- * 概率性出现插屏广告
- * @param ratio 触发概率,范围0-1,例如0.2表示20%概率
- */
- public static ratioInterstitialAd(ratio: number = 0.2) {
- const clampedRatio = Math.min(ratio,1);
- //生成0-1的随机数,小于等于ratio时触发
- if(Math.random() <= clampedRatio) {
- PlatformSystem.platform.showInterstitialAd();
- }
- }
- }
- /**
- * //摄像机正前方是跟随枪
- const targetPos = Game.I.player.node.worldPosition;
- const playerRotation = Game.I.player.node.worldRotation;
- const offset = new Vec3(0, 2, -8); // 上方2单位,后方5单位
- // 计算世界空间偏移
- const worldOffset = Vec3.transformQuat(new Vec3(),
- offset,
- playerRotation
- );
- Game.I.camera.node.worldPosition = Vec3.add(new Vec3(), targetPos, worldOffset);
- Game.I.camera.node.lookAt(targetPos);
- let crossPos: Vec3 = Utils.getScreenCenterWorldPos(Game.I.camera);
- const direction = Vec3.subtract(new Vec3(), crossPos, gun.node.worldPosition);
- direction.normalize();
- Game.I.camera.node.forward = direction;
- */
|