/** 游戏中一些通用方法 */ export module GameUtils{ //================================================运行设备/大环境检查================================================// /** 是否运行在微信浏览器内 */ export function isWeiXin():boolean{ return Laya.Browser.onWeiXin && isWeb(); } /** 是否运行在移动设备 */ export function isMobile():boolean{ return Laya.Browser.onMobile; } /** 是否运行在APP设备上 */ export function isAPP():boolean{ return Boolean(Laya.Browser.window.conch); } /** 是否运行在微信小游戏环境中 */ export function isMini():boolean{ return typeof GameGlobal != 'undefined'; } /** 是否运行在浏览器环境中 */ export function isWeb():boolean{ return Boolean(Laya.Browser.window.isWeb); } /** 是否是窄屏 */ export function isNarrowScreen():boolean{ return Laya.stage.width/Laya.stage.height > 2; } /** 是否是安卓系统 */ export function isAndriod():boolean{ return Laya.Browser.onAndroid || Laya.Browser.window.navigator.platform.toLowerCase() == 'android'; } /** 是否是iOS系统 */ export function isIOS():boolean{ return Laya.Browser.onIOS && Laya.Browser.window.navigator.platform.toLowerCase() == 'ios'; } //========================================= 数组操作 =========================================// /** 检查一个元素是否在数组中,若在则将其从数组中移除,否则添加进数组 * @param elem 欲检查的元素 * @param ary 欲操作的数组 * @return 返回值表示元素是否被添加入数组 */ export function toggleArrayElem(elem, ary:any[]){ let index = ary.indexOf(elem); if(index >= 0) { ary.splice(index, 1); return false; } ary.push(elem); return true; } /** * 如果一个元素不在指定数组中,则将其加入到该数组中 * @param elem * @param ary */ export function pushElemToAryIfNotIn(elem, ary:any[]){ if(_.isArray(elem)) { for(let i of elem) { pushElemToAryIfNotIn(i, ary); } } else { let index = ary.indexOf(elem); if(index === -1) { ary.push(elem); } } } /** * 如果一个元素在指定数组中,则将其从该数组中移除 * @param elem * @param ary */ export function removeElemFromAryIfIn(elem, ary:any[]){ let index = ary.indexOf(elem); if(index >= 0) { ary.splice(index, 1); } } /** * 把数组from中所有元素添加到ary数组中去 * @param ary * @param from */ export function getAryElemFromAnotherAry(ary:any[], from:any[]){ if(!ary || !from)return; for(let i of from) { ary.push(i); } } /** * 遍历一个数组 * @param ary 遍历对象 * @param loopFuc 遍历回调函数。接受两个参数表示当前遍历对象及对象所在索引号,此回调函数若是返回一个非falsy的值则会中断遍历并使aryForEach方法返回此值 * @param reverse 是否逆序遍历 */ export function aryForEach(ary:any[], loopFuc:(elem:any, index:number) => any, reverse=false):any{ if(!ary || ary.length === 0)return; let i:number; let oldLen:number; let fucReturn:any; if(reverse) { for(i = ary.length - 1; i >= 0; i--) { oldLen = ary.length; fucReturn = loopFuc(ary[i], i); if(fucReturn)return fucReturn; i -= oldLen - ary.length; } } else { for(i = 0; i < ary.length; i++) { oldLen = ary.length; fucReturn = loopFuc(ary[i], i); if(fucReturn !== undefined)return fucReturn; i -= oldLen - ary.length; } } } /** * 根据某种条件来切割数组,遇到第一个匹配的对象就会停止搜索直接返回切分结果。若ary无法被切割,则返回ary本身 * @param ary * @param fuc 切分条件定义函数,返回true表示拆分 * @returns 切分结果,遇到第一个匹配的对象match时会开始切分,将ary切成三份,match前的部分,match本身,match后的部分 */ export function splitAryByFuc(ary:any[], fuc:(elem) => boolean){ let len = ary.length; for(let i = 0; i < len; i++) { if(fuc(ary[i])) { return [ary.slice(0, i), ary[i], ary.slice(i + 1)]; } } return ary; } /** * 合并多个长度一致的数组为一个。例如:aryMerge([[1,2],[3,4]], ['id', 'name']) => [{id:1, name:3}, {id:2, name:4}] * @param {Array[]} aryList 二维数组,里面的数组项是需要合并的数组引用,每个数组代表一个键 * @param {string[]} keyList 合并后的数组项所拥有的键名列表 * @param {boolean} lengthEqual 数组长度是否相等,如果相等,那么合并后会取最小的长度 */ export function aryMerge(aryList:any[][], keyList:string[], lengthEqual = true){ if(!aryList)return; let result = []; if(aryList.length > 0) { let i; let minLen = Number.MAX_VALUE; //统一数组长度 if(lengthEqual) { for(let aryListElem of aryList) { if(aryListElem.length < minLen)minLen = aryListElem.length; } for(i = 0; i < aryList.length; i++) { aryList[i] = aryList[i].slice(0, minLen); } } let len = aryList[0].length; let keyLen = keyList.length; for(i = 0; i < len; i++) { let d = {}; for(let j = 0; j < keyLen; j++) { d[keyList[j]] = aryList[j][i]; } result.push(d); } } return result; } /** * 将一个数字数组转换成字符串数组 * @param ary */ export function numAryToStrAry(ary:number[]):string[]{ let result:string[] = []; for(let i=0; i 0 ? 1 : -1) * Math.floor(Math.abs(number)); }; var maxSafeInteger = Math.pow(2, 53) - 1; var toLength = function (value) { var len = toInteger(value); return Math.min(Math.max(len, 0), maxSafeInteger); }; // 1. Let C be the this value. var C = this; // 2. Let items be ToObject(arrayLike). var items = Object(arrayLike); // 3. ReturnIfAbrupt(items). if (arrayLike == null) { throw new TypeError("Array.from requires an array-like object - not null or undefined"); } // 4. If mapfn is undefined, then let mapping be false. var T; if (typeof mapFn !== 'undefined') { // 5. else // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. if (!isCallable(mapFn)) { throw new TypeError('Array.from: when provided, the second argument must be a function'); } // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. if (thisArg) { T = thisArg; } } // 10. Let lenValue be Get(items, "length"). // 11. Let len be ToLength(lenValue). var len = toLength(items.length); // 13. If IsConstructor(C) is true, then // 13. a. Let A be the result of calling the [[Construct]] internal method // of C with an argument list containing the single item len. // 14. a. Else, Let A be ArrayCreate(len). var A = isCallable(C) ? Object(new C(len)) : new Array(len); // 16. Let k be 0. var k = 0; // 17. Repeat, while k < len… (also steps a - h) var kValue; while (k < len) { kValue = items[k]; if (mapFn) { A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); } else { A[k] = kValue; } k += 1; } // 18. Let putStatus be Put(A, "length", len, true). A.length = len; // 20. Return A. return A; } /** * 搜索一个元素elem在数组ary中出现的次数 * @param elem * @param ary * @param startIndex 起始数组索引位置。默认为0 */ export function getElementCountInArray(elem:T, ary:T[], startIndex:number=0):number{ let r:number = 0; for(let i=startIndex; i < ary.length; i++) { if(ary[i] == elem)r++; } return r; } /** * 往一个数组里追加数据多次。pushToAryMutiTimes('a', [], 3) => ['a','a','a'] * @param elem * @param ary * @param times */ export function pushToAryMutiTimes(elem:T, ary:T[], times:number){ for(let i = 0; i < times; i++) { ary.push(elem); } return ary; } /** * 一般来说数组排序都会使用Array.sort(compareFuc)方法进行排序,但是当compareFuc返回0的时候会造成某些浏览器中数组发生乱序问题。 * 因此,当compareFuc有可能返回0的情况下需要使用该方法对数组进行排序 */ export function arySort(ary:any[], compareFuc:(a,b)=>number){ let origiAry = ary.concat(); ary.sort((a, b)=>{ let r = compareFuc(a, b); if(r === 0)r = origiAry.indexOf(a) - origiAry.indexOf(b); return r; }); } /** * 搜索一个数组中符合条件的元素 * @param ary 搜索对象 * @param searchCondition 搜索条件【{[key:string]:value}】或者方法【(elem)=>boolean】 * @param mode 使用模式。包含'g'表示启用贪婪模式进行搜索,返回结果不再是一个单独元素而是一个数组;包含'd'表示启用删除模式,找到目标对象之后立马执行删除操作,和'g'模式一起使用可以删除所有找到的元素 */ export function searchAry(ary:any[], searchCondition:any, mode?:string){ if(!ary)return; let r; if(!mode)mode = ''; let globalMode = mode.indexOf('g') != -1; let deleteMode = mode.indexOf('d') != -1; if(globalMode)r = []; let isConditionFuction = typeof searchCondition == 'function'; for(let i of ary) { let isMatch = true; if(isConditionFuction) { isMatch = (searchCondition as (elem)=>boolean)(i); } else { for(let p in searchCondition) { if(i[p] != searchCondition[p]) { isMatch = false; break; } } } if(isMatch) { if(r){ r.push(i); }else{ r = i; break; } } } if(deleteMode) { let deleteOne = (deleteElem)=>{ let deleteIndex = ary.indexOf(deleteElem); if(deleteIndex != -1) { ary.splice(deleteIndex, 1); } } if(_.isArray(r)) { r.forEach(deleteOne); } else { deleteOne(r); } } return r; } export function cloneAry(ary:any[], deepClone:boolean=false){ if(!deepClone)return ary.concat(); let r = []; for(let i = 0; i < ary.length; i++) { r[i] = cloneObject(ary[i]); } return r; } /** 我的数组中的所有元素是否全部都在另一个数组中出现 */ export function isAllElememtsInOtherAry(myAry:any[], otherAry:any[], allowOtherAryHasLeftElements:boolean=true){ otherAry = otherAry.concat(); for(let i of myAry) { let index = otherAry.indexOf(i); if(index === -1)return false; otherAry.splice(index, 1); } return !allowOtherAryHasLeftElements ? otherAry.length == 0 : true; } /** * 克罗内克积。比如传入[['a1','a2'], ['b1','b2']]将会得到[['a1','b1'],['a1','b2'],['a2','b1'],['a2','b2']] */ export function kroneckerProduct(input:any[][], filter?:(elem:any[])=>boolean, map?:(elem:any[])=>any){ let output = []; let len = input.length; if(len > 1) { let ary1 = input[0]; if(len > 2) { let leftAryElemProduct = kroneckerProduct(input.slice(1)); for(let i of ary1) { for(let j of leftAryElemProduct) { let o = [i].concat(j); if(map)o = map(o); if(filter && !filter(o)) { continue; } output.push(o); } } } else { let ary2 = input[1]; for(let i of ary1) { for(let j of ary2) { let o = [i, j]; if(map)o = map(o); if(filter && !filter(o)) { continue; } output.push(o); } } } } else { output = _.flatten(input); if(map)output = output.map(map); if(filter)output = output.filter(filter); } return output; } //========================================= 字符串操作 =========================================// /** * 然后一个字符串重复count次后的值 * @param str * @param count * @param joinStr 所有重复字符之间追加的分隔符 */ export function repeatString(str:string, count:number, joinStr=''){ let result = ''; for(let i = 0; i < count; i++) { result += str; if(i < count - 1) { result += joinStr; } } return result; } const SecondsPerMinute = 60; const MinutesPerHour = 60; const HoursPerDay = 24; const SecondsPerHour = SecondsPerMinute * MinutesPerHour; const SecondsPerDay = SecondsPerHour * HoursPerDay; function toDateString(Y:number, M:number, D:number, h:number, m:number, s:number, format:string){ return format.replace(/YYYY/g, Y + '').replace(/MM/g, M < 10 ? '0' + M : M + '').replace(/M/g, M + '').replace(/DD/g, D < 10 ? '0' + D : D + '').replace(/D/g, D + '') .replace(/hh/g, h < 10 ? '0' + h : h + '').replace(/h/g, h + '').replace(/mm/g, m < 10 ? '0' + m : m + '').replace(/m/g, m + '').replace(/ss/g, s < 10 ? '0' + s : s + '').replace(/s/g, s + ''); } /** * 将秒转化为时间格式字符串,最大支持转换到天 * @param {number} sec 秒数 * @param {string} format 字符串格式 */ export function secToDateString(sec:number, format:string){ let D = Math.floor(sec / SecondsPerDay); sec = sec % SecondsPerDay; let h = Math.floor(sec / SecondsPerHour); sec = sec % SecondsPerHour; let m = Math.floor(sec / SecondsPerMinute); sec = sec % SecondsPerMinute; return toDateString(0, 0, D, h, m, sec, format); } /** * 使用若干个参数来替换某个字符串中的占位符。占位符表示格式类似于“{n}”的字符,n表示从1开始的索引号 * @param {string} str 操作字符串 */ export function replacePlaceHolder(str:string, ...arg){ if(arg.length < 1)return str; for(let i = 0; i < arg.length; i++) { str = str.replace(`{${i+1}}`, arg[i]); } return str; } /** * 使用一个字典(Dictionary),即一个键值对对象来替换某个字符串中的占位符。占位符类似于"{key}"的字符 * @param str * @param dic */ export function replacePlaceHolderByDic(str:string, dic:{[key:string]:string}){ for(let p in dic) { str = str.replace(`{${p}}`, dic[p]); } return str; } export function dateToString(date:Date, format:string){ return toDateString(date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), format); } var UNIT_TEXT = [['10000','万', '/'],['100000000','亿','/']]; /** * 降低一个数字的零的个数并更换数字单位,小数点前后数字个数不超过五位 * @param {Number} targetNumber * @param {boolean} useSystemFont 是否将函数返回的字符串用在使用了系统字体的Label组件上,若为false,将会使用UNIT_TEXT中元素的第三个字符作为单位,否则使用第二个字符 * @param numberTotalCount 传入该值可以用于控制小数的位数,该值可以理解为包括小数的数字总位数,例:该值为4,则512345转换成51.2345万后会将总数字位数限制为4,因为整数部分已有2位,故留给小数的位数仅剩2位,最后结果是51.23万 */ export function reduceZeroNumber(targetNumber:number, useSystemFont=true, numberTotalCount:number=5):number{ let r:number = aryForEach(UNIT_TEXT, (u) => { let [unit, text, customFontChar] = u; if(targetNumber > unit) { let ary = (targetNumber / unit).toString().split('.'); if(ary.length > 1) { let leftCount = numberTotalCount - ary[0].length; if(leftCount > 0) { ary[1] = ary[1].substr(0, leftCount); } else { ary.splice(1, 1); } } return useSystemFont ? ary.join('.') + text : ary.join('.') + customFontChar; } }, true); return r ? r : targetNumber; } /** 格式化银子数目,把字符串转化为每3个一个逗号的金钱格式 */ export function formatMoney(str){ let len = str.length, str2 = ''; let max = len % 3 == 0 ? Math.floor(len / 3) - 1 : Math.floor(len / 3); for(var i = 0 ; i < max ; i++){ var s = str.slice(len - 3, len); str = str.substr(0, len - 3); str2 = (',' + s) + str2; len = str.length; } str += str2; return str; } /** * 重复一个字符串多次,每次重复后会追加一个逗号。stringMultiply('test', 2) => 'test,test' * @param s * @param multiplier */ export function stringMultiply(s:string, multiplier:number):string{ let r = []; let i; let j; //s是数组的话,对数组中所有元素均乘以multiplier if(_.isArray(s)) { for(i of s) { for(j = 0; j < multiplier; j++) { r.push(i); } } } //s是字符串,则直接乘以multiplier else { for(i = 0; i < multiplier; i++) { r.push(s); } } return r.join(','); } function isHTTP(v:string, isSafeURL:boolean){ let searchStr = isSafeURL ? 'https://' : 'http://'; return v.indexOf(searchStr) == 0; } /** 判断一个字符串是否是HTTP或者HTTPS开头的路径 */ export function isHTTPPath(v:string):boolean{ return isHTTP(v, false) || isHTTP(v, true); } /** 将HTTP链接转换成HTTPS链接 */ export function toHTTPSPath(v:string):string{ if(isHTTP(v, false)) { return v.replace('http://', 'https://'); } return v; } export function isPngPath(v:string):boolean{ return v.indexOf('png') != -1; } export function isJPGPath(v:string):boolean{ return v.indexOf('jpg') != -1; } export function isImgPath(v:string):boolean{ return isPngPath(v) || isJPGPath(v); } /** * 把字符串转换为ascii码的字符串 * @param str 要换转的字符串 * @param radix 数字的基数,进制 * @param separator 各个字符之间的链接符号 */ export function stringToAscIICode(str:string, radix:number = 10, separator:string = ''):string{ if(str === "")     return ""; let hexCharCode = [];   for(let i = 0; i < str.length; i++) {     hexCharCode.push((str.charCodeAt(i)).toString(radix));   }   return hexCharCode.join(separator); } /** * 将服务器传过来的配置属性,例如:'a:1,b:3,c:2'转换成数组a a[0]= [a,1] a[1] = [b,3]... */ export function changeServerStringToArray(v:string){ let arry = []; var stringArray = v.split(','); aryForEach(stringArray,(elem:string, index:number)=>{ if(elem.length > 0) { let detailArray = elem.split(':'); arry.push(detailArray); } }); return arry; } /** 将对象转换为字符串。其先用JSON.stringify尝试转换,转换失败则会返回参数本身 */ export function jsonToString(obj){ let r; if(typeof obj === 'object') { try{ r = JSON.stringify(obj); } catch(e){ r = obj; } } else if(typeof obj === 'undefined') { r = 'undefined' } return r; } //========================================= 时间操作 =========================================// /** * 从日期字符串转换成Date对象 * @param {string} str MM-DD hh:mm */ export function getDateFromString(str:string){ let result = new Date(); let [YMD, hm] = str.split(' '); let ary = YMD.split('-'); let Y:number; if(ary.length === 3){ Y = Number(ary.shift()); result.setFullYear(Y); } let [M, D] = strAryToNumAry(ary); M -= 1; let [h, m] = strAryToNumAry(hm.split(':')); result.setMonth(M); result.setDate(D); result.setHours(h); result.setMinutes(m); return result; } /** * 获得某个时间点相对于当前的时间差,精确到天 * @param {Date} targetDate 比较对象时间 * @param {Number} showFullDateCondition 相差多少天以上会显示完整日期 * @param {boolean} ignoreToday 是否忽略今天,如果忽略,那么‘今天’字符串将被转换成空字符串返回 */ export function getRelativeDay(targetDate:Date, showFullDateCondition:number, ignoreToday=false){ let now = new Date(); targetDate = new Date(targetDate.valueOf()); //时分秒清零 now.setHours(0, 0, 0); targetDate.setHours(0, 0, 0); let deltaSeconds = Math.floor((targetDate.getTime() - now.getTime()) / 1000); let deltaDay = Math.floor(deltaSeconds / SecondsPerDay); if(Math.abs(deltaDay) <= showFullDateCondition) { switch(deltaDay){ case -1: return '昨天'; case -2: return '前天'; case -3: return '大前天'; case 0: return ignoreToday ? '' : '今天'; case 1: return '明天'; case 2: return '后天'; case 3: return '大后天'; } } return toDateString(null, targetDate.getMonth() + 1, targetDate.getDate(), null, null, null, 'MM-DD'); } export function getPassedTime(targetDate:Date, showFullDateCondition=2){ let now = new Date(); let deltaSeconds = Math.floor((now.getTime() - targetDate.getTime()) / 1000); let deltaMinutes = Math.floor(deltaSeconds / SecondsPerMinute); let deltaHour = Math.floor(deltaMinutes / MinutesPerHour); let deltaDay = Math.floor(deltaHour / HoursPerDay); if(deltaDay > 0){ return getRelativeDay(targetDate, showFullDateCondition); } else if(deltaHour > 0){ return `${deltaHour}小时前`; } else if(deltaMinutes > 5){ return `${deltaMinutes}分钟前`; } return '刚刚'; } //========================================= 对象操作 =========================================// /** * 克隆一个对象 * @param obj */ export function cloneObject(obj){ if(!obj)return obj; if(typeof obj['clone'] == 'function')return obj['clone'](); let result = {}; for(let p in obj) { let d = obj[p]; if(_.isObject(d)) { if(_.isArray(d)) { result[p] = d.concat(); } else { result[p] = cloneObject(d); } } else { result[p] = d; } } return result; } /** * 获取一个对象的第一个键值 * @param obj */ export function getFirstProperty(obj):{key:string, value:any}{ let result; if(obj) { for(let p in obj) { result = {key:p, value:obj[p]}; } } return result; } /** * 根据值来获得键名 * @param obj * @param value */ export function getKeyByValue(obj, value){ for(let p in obj) { if(obj[p] === value) { return p; } } } /** * 将一个function加到另一个function后面,使它能够在该function被执行完毕后能够被执行 * @param {Function} target 被拓展者 * @param {Function} appendWith 拓展内容 * @returns {Function} 一个新的Function对象,它会先执行target,然后再执行appendWith */ export function functionAppend(target:Function, appendWith:Function) : any{ if(!target)return; return function(...arg){ target && target.apply(this, arg); appendWith && appendWith.apply(this, arg); } } /** * 根据一个类的名字取得该类的够造函数 * @param n 类名字 */ export function getClassConstructorByName(n):new()=>any{ return Laya.ClassUtils.getRegClass(n); } /** * 根据一个类的名字创建一个该类的实例 * @param n 类名字 */ export function getClassInstanceByName(n):any{ return Laya.ClassUtils.getInstance(n); } /** * 从服务端数据快速创建出一个VO数据对象 * @param voClass VO数据对象够造函数 * @param serverData 服务端数据 * @param keyMap 属性映射表:{服务端数据属性名:拷贝到的vo属性名} */ export function createVOFromServer(voClass, serverData, keyMap:{[key:string]:string}){ let r = new voClass(); for(let p in keyMap) { if(serverData.hasOwnProperty(p)) { r[keyMap[p]] = serverData[p]; } } return r; } /** 将一个Object转换为数组 */ export function obj2Ary(obj):any[]{ let r:any[] = []; for(let p in obj) { let d = obj[p]; if(_.isArray(d)) { r = r.concat(d); } else { r.push(d); } } return r; } export function objAssign(target, ...source) { // .length of function is 2 if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 0; index < source.length; index++) { var nextSource = source[index]; if (nextSource != null) { // Skip over if undefined or null for (var nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; } /** 解密ticket为一个数据数组 */ export function decodeTicket(ticket:string):string[] { let encodeInfo = unescape(ticket); let decrypted = DesDecode(encodeInfo); return decrypted.split('|'); } /** 如果参数为null, undefined, 空字符串, NaN或者'null'和'undefined'这两个后端经常会给到的空数据字符时返回true */ export function isNull(v){ return typeof v === 'undefined' || v === null || isNaN(v) || v === '' || v === 'null' || v === 'undefined'; } /** 将一个变量转换为Number类型,若转换结果为NaN则返回0 */ export function toNumber(target):number{ let r = Number(target); return isNaN(r) ? 0 : r; } /** 从一个对象拷贝一些属性值到另一个对象 */ export function copyProperties(from, to, propertyList:string[]){ for(let i of propertyList) { to[i] = from[i]; } return to; } //========================================= 数字运算操作 =========================================// /** * 随机出一个结果 * @param {*[]} options 可选项数组 * @param {Number[]} possibility 每个选项被选中的可能性,从0-1分别代表0%-100%。该数组中元素需要和options中元素一一对应 */ export function random(options:any[], possibility:number[]):any{ let roll = Math.random(); possibility = possibility.concat(); let i; for(i = 1; i < possibility.length; i++) { possibility[i] += possibility[i - 1]; } for(i=0; i