You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
11 KiB
368 lines
11 KiB
import { Component, rect, Rect, Vec2, Node } from "cc";
|
|
|
|
|
|
enum TimeUnit {
|
|
S,
|
|
M,
|
|
H,
|
|
D
|
|
}
|
|
|
|
/**
|
|
* 工具类
|
|
*/
|
|
export default class Tool {
|
|
/**
|
|
* 深拷贝
|
|
* @param source 源数据
|
|
*/
|
|
public static deepCopy<T>(source: T): T {
|
|
if (typeof source !== "object" || source === null || source instanceof RegExp) {
|
|
return source;
|
|
}
|
|
|
|
let result: any = null;
|
|
if (Array.isArray(source)) {
|
|
result = [];
|
|
for (let item of source) {
|
|
result.push(this.deepCopy(item));
|
|
}
|
|
} else {
|
|
result = {};
|
|
for (let key in source) {
|
|
result[key] = this.deepCopy(source[key]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 异步等待 - setTimeout
|
|
* @param seconds
|
|
*/
|
|
public static wait(seconds: number): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, seconds * 1000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 异步等待 - Component.scheduleOnce
|
|
*/
|
|
public static waitCmpt(cmpt: Component, seconds: number): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
cmpt.scheduleOnce(() => {
|
|
resolve();
|
|
}, seconds);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 线性插值
|
|
* @param a 起始值
|
|
* @param b 目标值
|
|
* @param r ratio between 0 and 1
|
|
* @param min 最小间隔值
|
|
*/
|
|
public static lerp(a: number, b: number, r: number, min: number = 0): number {
|
|
min = Math.abs(min);
|
|
let c = b - a;
|
|
let delta = c * r;
|
|
delta = delta < 0 ? Math.min(delta, -min) : Math.max(delta, min);
|
|
if (Math.abs(delta) > Math.abs(c)) {
|
|
delta = c;
|
|
}
|
|
return a + delta;
|
|
}
|
|
|
|
/**
|
|
* 通过两点坐标(不平行于坐标轴)和x,计算两点式方程结果y
|
|
*/
|
|
public static calcTwoPointForm(p1: Vec2, p2: Vec2, x: number): number {
|
|
if (p1.x === p2.x) return p1.y;
|
|
return (p2.y - p1.y) * (x - p1.x) / (p2.x - p1.x) + p1.y;
|
|
}
|
|
|
|
/**
|
|
* 返回两个矩形的重叠矩形,不重叠则返回null
|
|
*/
|
|
public static overlapRect(r1: Rect, r2: Rect): Rect {
|
|
let xMin = Math.max(r1.xMin, r2.xMin);
|
|
let xMax = Math.min(r1.xMax, r2.xMax);
|
|
let yMin = Math.max(r1.yMin, r2.yMin);
|
|
let yMax = Math.min(r1.yMax, r2.yMax);
|
|
|
|
if (xMin > xMax || yMin > yMax) {
|
|
return null;
|
|
}
|
|
return rect(xMin, yMin, xMax - xMin, yMax - yMin);
|
|
}
|
|
|
|
/**
|
|
* 返回value是否在 [min, max] 区间内
|
|
* @param min
|
|
* @param max
|
|
* @param value
|
|
* @param includeEdge true(默认值): [min, max]; false: (min, max)
|
|
*/
|
|
public static inRange(min: number, max: number, value: number, includeEdge: boolean = true): boolean {
|
|
return includeEdge ? value >= min && value <= max : value > min && value < max;
|
|
}
|
|
|
|
/**
|
|
* 获取区间[min, max)的整数,传入1个参数则区间为[0, min)
|
|
*/
|
|
public static randInt(min: number, max: number = undefined): number {
|
|
if (max === undefined) {
|
|
max = min;
|
|
min = 0;
|
|
}
|
|
min = Math.ceil(min);
|
|
max = Math.floor(max);
|
|
return Math.floor(Math.random() * (max - min)) + min;
|
|
}
|
|
|
|
/**
|
|
* 获取区间[min, max)的浮点数,传入1个参数则区间为[0, min)
|
|
*/
|
|
public static randFloat(min: number, max: number = undefined): number {
|
|
if (max === undefined) {
|
|
max = min;
|
|
min = 0;
|
|
}
|
|
return Math.random() * (max - min) + min;
|
|
}
|
|
|
|
/**
|
|
* 根据权重数组进行随机,返回结果下标
|
|
* @param weightArr 权重数组
|
|
* @returns 随机到的权重数组下标
|
|
*/
|
|
public static randWeightIdx(weightArr: number[]) {
|
|
let sum = 0;
|
|
for (let i = 0; i < weightArr.length; i++) {
|
|
sum += weightArr[i];
|
|
}
|
|
let randNum = this.randFloat(0, sum);
|
|
let curValue = 0
|
|
for (let i = 0; i < weightArr.length; i++) {
|
|
curValue += weightArr[i];
|
|
if (randNum < curValue) {
|
|
return i;
|
|
}
|
|
}
|
|
return weightArr.length - 1;
|
|
}
|
|
|
|
/**
|
|
* Fisher–Yates shuffle 字符串随机乱序
|
|
*/
|
|
public static shuffleString(str: string): string {
|
|
let arr: string[] = [];
|
|
for (let i = 0; i < str.length; i++) {
|
|
arr.push(str[i]);
|
|
}
|
|
arr = this.shuffle(arr);
|
|
str = "";
|
|
arr.forEach((v) => {
|
|
str += v;
|
|
});
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Fisher–Yates shuffle 数组随机乱序
|
|
*/
|
|
public static shuffle<T>(arr: Array<T>): Array<T> {
|
|
for (let i = arr.length - 1; i >= 0; i--) {
|
|
let randomIndex = Math.floor(Math.random() * (i + 1));
|
|
[arr[randomIndex], arr[i]] = [arr[i], arr[randomIndex]];
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
/**
|
|
* 随机返回数组中的一个元素
|
|
*/
|
|
public static arrayRand<T>(arr: Array<T>): T {
|
|
if (arr.length <= 0) {
|
|
return null;
|
|
}
|
|
return arr[this.randInt(0, arr.length)];
|
|
}
|
|
|
|
/**
|
|
* 判断数组中是否有某个元素
|
|
* @param arr 数组
|
|
* @param param 元素值或表达元素值满足某种条件的函数
|
|
*/
|
|
public static arrayHas<T>(arr: T[], param: T | ((ele: T) => boolean)): boolean {
|
|
let idx = typeof param !== "function" ? arr.findIndex((e) => { return e === param; }) : arr.findIndex(param as ((ele: T) => boolean));
|
|
return idx >= 0;
|
|
}
|
|
|
|
/**
|
|
* 根据下标交换数组两个元素位置
|
|
*/
|
|
public static arraySwap<T>(arr: T[], idx1: number, idx2: number): void {
|
|
if (idx1 === idx2 || !this.inRange(0, arr.length - 1, idx1) || !this.inRange(0, arr.length - 1, idx2)) {
|
|
return;
|
|
}
|
|
[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
|
|
}
|
|
|
|
/**
|
|
* 将元素从fromIdx位置移到toIdx位置,其余元素相对位置不变
|
|
*/
|
|
public static arrayMove<T>(arr: T[], fromIdx: number, toIdx: number): void {
|
|
if (fromIdx === toIdx || !this.inRange(0, arr.length - 1, fromIdx) || !this.inRange(0, arr.length - 1, toIdx)) {
|
|
return;
|
|
}
|
|
let from: T[] = arr.splice(fromIdx, 1);
|
|
arr.splice(toIdx, 0, from[0]);
|
|
}
|
|
|
|
/**
|
|
* 在数组中添加某个元素
|
|
* @param canRepeat 是否可重复添加相同元素 默认false
|
|
* @return 是否执行了添加行为
|
|
*/
|
|
public static arrayAdd<T>(arr: T[], ele: T, canRepeat: boolean = false): boolean {
|
|
if (!canRepeat && this.arrayHas(arr, ele)) {
|
|
return false;
|
|
}
|
|
arr.push(ele);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 在数组中删除某个元素(若有多个相同元素则只删除第一个)
|
|
* @return 是否执行了删除行为
|
|
*/
|
|
public static arrayDelete<T>(arr: T[], ele: T): boolean {
|
|
let index: number = arr.findIndex((e) => { return e === ele; });
|
|
if (index >= 0) {
|
|
arr.splice(index, 1);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 对单位为秒的时间生成格式化时间字符串
|
|
* @param sec 时间s
|
|
* @param format 格式化字符串
|
|
* @example
|
|
* // 当format为string时,会以format中的最大时间单位进行格式化
|
|
* Tool.formatTimeString(3601, "m:s"); // 60:1
|
|
* Tool.formatTimeString(3601, "mm:ss"); // 60:01
|
|
* Tool.formatTimeString(3601, "hh:mm:ss"); // 01:00:01
|
|
*
|
|
* // 当format为object时,会以传入的sec计算最大的时间单位,并选择format对应的字符串进行格式化
|
|
* Tool.formatTimeString(100, {
|
|
* S: "s秒",
|
|
* M: "m分s秒",
|
|
* H: "h时m分s秒",
|
|
* D: "d天h时m分s秒"
|
|
* }); // 1分40秒
|
|
* Tool.formatTimeString(100000, {
|
|
* S: "s秒",
|
|
* M: "m分s秒",
|
|
* H: "h时m分s秒",
|
|
* D: "d天h时m分s秒"
|
|
* }); // 1天3时46分40秒
|
|
*/
|
|
public static formatTimeString(sec: number, format: string | { "S": string; "M": string; "H": string; "D": string } = "hh:mm:ss"): string {
|
|
let seconds: number = Math.floor(sec);
|
|
let minutes: number = Math.floor(seconds / 60);
|
|
let hours: number = Math.floor(seconds / 3600);
|
|
let days: number = Math.floor(seconds / 86400);
|
|
|
|
let maxUnit: TimeUnit = TimeUnit.S;
|
|
let result: string = "";
|
|
|
|
if (typeof format === "string") {
|
|
// 查询格式化字符串中最大的单位
|
|
result = format;
|
|
if (/d/i.test(format)) {
|
|
maxUnit = TimeUnit.D;
|
|
} else if (/h/i.test(format)) {
|
|
maxUnit = TimeUnit.H;
|
|
} else if (/m/i.test(format)) {
|
|
maxUnit = TimeUnit.M;
|
|
}
|
|
} else {
|
|
// 以传入的数值判断最大单位
|
|
if (days > 0) {
|
|
maxUnit = TimeUnit.D;
|
|
result = format.D;
|
|
} else if (hours > 0) {
|
|
maxUnit = TimeUnit.H;
|
|
result = format.H;
|
|
} else if (minutes > 0) {
|
|
maxUnit = TimeUnit.M;
|
|
result = format.M;
|
|
} else {
|
|
maxUnit = TimeUnit.S;
|
|
result = format.S;
|
|
}
|
|
}
|
|
|
|
if (maxUnit > TimeUnit.S) {
|
|
seconds %= 60;
|
|
}
|
|
if (maxUnit > TimeUnit.M) {
|
|
minutes %= 60;
|
|
}
|
|
if (maxUnit > TimeUnit.H) {
|
|
hours %= 24;
|
|
}
|
|
|
|
let data = {
|
|
d: days,
|
|
hh: hours < 10 ? `0${hours}` : `${hours}`,
|
|
h: hours,
|
|
mm: minutes < 10 ? `0${minutes}` : `${minutes}`,
|
|
m: minutes,
|
|
ss: seconds < 10 ? `0${seconds}` : `${seconds}`,
|
|
s: seconds
|
|
};
|
|
|
|
result = result.toLowerCase();
|
|
for (const key in data) {
|
|
const value = data[key];
|
|
result = result.replace(new RegExp(key, "g"), value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 子节点递归处理
|
|
* @param node 需要递归处理的节点或节点数组
|
|
* @param cb 节点处理函数
|
|
* @param thisArg cb绑定的this对象
|
|
*/
|
|
public static nodeRecursive(node: Node | Node[], cb: (n: Node) => void, thisArg: any = undefined): void {
|
|
if (node instanceof Node) {
|
|
cb.call(thisArg, node);
|
|
node.children.forEach((n: Node) => { this.nodeRecursive(n, cb, thisArg); });
|
|
} else if (Array.isArray(node)) {
|
|
node.forEach((n: Node) => { this.nodeRecursive(n, cb, thisArg); });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* destroy并立即remove传入节点的所有子节点
|
|
*/
|
|
public static clearChildren(...nodes: Node[]): void {
|
|
nodes.forEach((e) => {
|
|
e.destroyAllChildren();
|
|
e.removeAllChildren();
|
|
});
|
|
}
|
|
}
|
|
|