ddgame_kit
Version:
DDKit - 游戏开发工具库
1,567 lines (1,559 loc) • 92.9 kB
JavaScript
/**
* time: 2023/12/07 16:33:11
* desc: 扩展Promise
*/
class PromiseEx {
static delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
/** @public */
function Singleton() {
return class SingletonBase {
static get ins() {
if (!SingletonBase.__$ins) {
SingletonBase.__$ins = new this();
}
return SingletonBase.__$ins;
}
};
}
/**
* @en The `Handler` class is an event handler class.
* It is recommended to create a `Handler` object from the object pool using the `Handler.create()` method to reduce the overhead of object creation. When a `Handler` object is no longer needed, it can be recovered to the object pool using `Handler.recover()`. Do not use this object after recovery, as doing so may lead to unpredictable errors.
* Note: Since mouse events also use this object pool, improper recovery and invocation may affect the execution of mouse events.
* @zh Handler 是事件处理器类。
* 推荐使用 Handler.create() 方法从对象池创建,减少对象创建消耗。创建的 Handler 对象不再使用后,可以使用 Handler.recover() 将其回收到对象池,回收后不要再使用此对象,否则会导致不可预料的错误。
* 注意:由于鼠标事件也用本对象池,不正确的回收及调用,可能会影响鼠标事件的执行。
*/
class Handler {
/**
* @en Constructor method.
* @param caller The execution context.
* @param method The handling function.
* @param args Function arguments.
* @param once Whether it should be executed only once.
* @zh 构造方法
* @param caller 执行域。
* @param method 处理函数。
* @param args 函数参数。
* @param once 是否只执行一次。
*/
constructor(caller = null, method = null, args = null, once = false) {
/**
* @en The scope of the object (this).
* @zh 执行域(this)。
*/
this.caller = null;
/**
* @en The handling method.
* @zh 处理方法。
*/
this.method = null;
/**
* @en Arguments passed to the handler method.
* @zh 参数。
*/
this.args = null;
/**
* @en Indicates whether the handler should be executed only once. If true, the handler will be recovered after execution.After recycling, it will be reused, default to false.
* @zh 表示是否只执行一次。如果为true,回调后执行recover()进行回收。回收后会被再利用,默认为false
*/
this.once = false;
/**@private */
this._id = 0;
this.setTo(caller, method, args, once);
Handler._count++;
}
/**
* 打印Handler._count
* 方便开发阶段查看开销
*/
static log() {
console.log("Handler._count: ------", Handler._count);
}
/**
* @en Sets the specified property values for this object.
* @param caller The scope of the object (this).
* @param method The callback method.
* @param args The arguments to be passed to the method.
* @param once Whether the handler should be executed only once. If true, the handler will be recovered after execution.
* @returns Returns the handler itself.
* @zh 设置此对象的指定属性值。
* @param caller 执行域(this)。
* @param method 回调方法。
* @param args 携带的参数。
* @param once 是否只执行一次,如果为true,执行后执行recover()进行回收。
* @returns 返回 handler 本身。
*/
setTo(caller, method, args, once = false) {
this._id = Handler._gid++;
this.caller = caller;
this.method = method;
this.args = args;
this.once = once;
return this;
}
/**
* @en Executes the handler.
* @zh 执行处理器。
*/
run() {
if (this.method == null)
return null;
var id = this._id;
var result = this.method.apply(this.caller, this.args);
this._id === id && this.once && this.recover();
return result;
}
/**
* @en Executes the handler with additional data.
* @param data Additional callback data, can be a single data or an Array (as multiple arguments).
* @zh 执行处理器,并携带额外数据。
* @param data 附加的回调数据,可以是单个数据或者数组(作为多参)。
*/
runWith(data) {
if (this.method == null)
return null;
var id = this._id;
if (data == null)
var result = this.method.apply(this.caller, this.args);
else if (!this.args && !data.unshift)
result = this.method.call(this.caller, data);
else if (this.args)
result = this.method.apply(this.caller, this.args.concat(data));
else
result = this.method.apply(this.caller, data);
this._id === id && this.once && this.recover();
return result;
}
/**
* @en Clears the references of the object.
* @zh 清理对象引用。
*/
clear() {
this.caller = null;
this.method = null;
this.args = null;
return this;
}
/**
* @en Clears the handler and recovers it to the Handler object pool.
* @zh 清理并回收到 Handler 对象池内。
*/
recover() {
if (this._id > 0) {
this._id = 0;
Handler._pool.push(this.clear());
}
}
/**
* @en Creates a Handler from the object pool. By default, the handler will execute once and then be recovered immediately. If automatic recovery is not desired, set the `once` parameter to false.
* @param caller The scope of the object (this).
* @param method The callback method.
* @param args The arguments to be passed to the callback method.
* @param once Whether the handler should be executed only once. If true, the handler will be recovered after execution.
* @returns Return the created handler instance.
* @zh 从对象池内创建一个 Handler,默认会执行一次并立即回收。如果不需要自动回收,设置 `once` 参数为 false。
* @param caller 执行域(this)。
* @param method 回调方法。
* @param args 回调方法的参数。
* @param once 是否只执行一次,如果为true,回调后执行 recover() 进行回收,默认为true。
* @return 返回创建的handler实例。
*/
static create(caller, method, args = null, once = true) {
if (Handler._pool.length)
return Handler._pool.pop().setTo(caller, method, args, once);
return new Handler(caller, method, args, once);
}
}
Handler._count = 0;
/**@private handler对象池*/
Handler._pool = [];
/**@private */
Handler._gid = 1;
const ITEM_LAYOUT = 4; //callback,target,args,flag(0-deleted,1-normal,2-once)
/**
* @en Delegate class for managing and invoking callbacks
* This class provides functionality to add, remove, and invoke callback functions.
* It supports one-time callbacks and can manage callbacks with different targets and arguments.
* @zh Delegate类,用于管理和调用回调函数
* 这个类提供了添加、移除和调用回调函数的功能。
* 它支持一次性回调,并且可以管理具有不同目标对象和参数的回调函数。
*/
class Delegate {
constructor() {
this._flag = 0;
this._items = [];
}
/**
* @en Add a callback function
* @param callback The callback function
* @param target The target object of the callback
* @param args Arguments for the callback
* @zh 添加回调函数
* @param callback 回调函数
* @param target 回调函数的目标对象
* @param args 回调函数的参数
*/
add(callback, target, args) {
let arr = this._items;
let index = arr.findIndex((value, index, arr) => value == callback && arr[index + 1] == target);
if (index != -1) {
arr[index + 2] = args;
arr[index + 3] = 1;
}
else
arr.push(callback, target, args, 1);
}
/**
* @en Add a callback function that will only be executed once
* @param callback The callback function
* @param target The target object of the callback
* @param args Arguments for the callback
* @zh 添加只执行一次的回调函数
* @param callback 回调函数
* @param target 回调函数的目标对象
* @param args 回调函数的参数
*/
once(callback, target, args) {
let arr = this._items;
let index = arr.findIndex((value, index, arr) => value == callback && arr[index + 1] == target);
if (index != -1) {
arr[index + 2] = args;
arr[index + 3] = 2;
}
else
arr.push(callback, target, args, 2);
}
/**
* @en Remove a callback function
* @param callback The callback function to remove
* @param target The target object of the callback
* @zh 移除回调函数
* @param callback 要移除的回调函数
* @param target 回调函数的目标对象
*/
remove(callback, target) {
let arr = this._items;
let index = arr.findIndex((value, index, arr) => value == callback && arr[index + 1] == target);
if (index != -1) {
if (this._flag != 0) {
arr[index + 3] = 0;
this._flag = 2;
}
else
arr.splice(index, ITEM_LAYOUT);
}
}
/**
* @en Clear all callback functions
* @zh 清除所有回调函数
*/
clear() {
let arr = this._items;
if (this._flag != 0) {
arr.forEach((value, index, arr) => { if (index % ITEM_LAYOUT == 3)
arr[index] = 0; });
this._flag = 2;
}
else {
arr.length = 0;
}
}
/**
* @en Clear all callback functions for a specific target
* @param target The target object
* @zh 清除指定目标对象的所有回调函数
* @param target 目标对象
*/
clearForTarget(target) {
if (!target)
return;
let arr = this._items;
if (this._flag != 0) {
arr.forEach((value, index, arr) => { if ((index % ITEM_LAYOUT == 1) && arr[index] == target)
arr[index + 2] = 0; });
this._flag = 2;
}
else {
let i = arr.length - ITEM_LAYOUT;
while (i >= 0) {
if (arr[i + 1] == target)
arr.splice(i, ITEM_LAYOUT);
i -= ITEM_LAYOUT;
}
}
}
/**
* @en Get the number of callback functions
* @zh 获取回调函数的数量
*/
get count() {
return this._items.length / ITEM_LAYOUT;
}
/**
* @en Invoke all callback functions
* @param args Arguments for the invocation
* @zh 调用所有回调函数
* @param args 调用参数
*/
invoke(...args) {
if (this._flag != 0)
return;
this._flag = 1;
let arr = this._items;
let cnt = arr.length;
for (let i = 0; i < cnt; i += ITEM_LAYOUT) {
if (0 == arr[i + 3])
continue;
let fixedArgs = arr[i + 2];
try {
if (fixedArgs != null)
arr[i].call(arr[i + 1], ...fixedArgs, ...args);
else
arr[i].call(arr[i + 1], ...args);
}
catch (err) {
console.error(err);
}
if (arr[i + 3] == 2) {
arr[i + 3] = 0;
this._flag = 2;
}
}
if (this._flag == 2) {
let cnt = arr.length;
let i = 0;
while (i < cnt) {
if (arr[i + 3] == 0) {
arr.splice(i, ITEM_LAYOUT);
cnt -= ITEM_LAYOUT;
continue;
}
else
i += ITEM_LAYOUT;
}
}
this._flag = 0;
}
}
/**
* @en The `EventDispatcher` class is the base class for all classes that dispatch events.
* @zh `EventDispatcher` 类是可调度事件的所有类的基类。
*/
class EventDispatcher {
/**
* @internal
* @protected
* @en Start listening to a specific event type.
* This method is called when a new event listener is added.
* @param type The event type to listen to.
* @zh 开始监听特定事件类型。
* 添加新的事件侦听器时调用此方法。
* @param type 要监听的事件类型。
*/
onStartListeningToType(type) {
}
/**
* @en Check if the EventDispatcher object has any listeners registered for a specific type of event.
* @param type The type of event.
* @returns True if a listener of the specified type is registered, false otherwise.
* @zh 检查 EventDispatcher 对象是否为特定事件类型注册了任何侦听器。
* @param type 事件的类型。
* @returns 如果指定类型的侦听器已注册,则值为 true;否则,值为 false。
*/
hasListener(type) {
let listeners = this._events && this._events[type];
return !!listeners && listeners.count > 0;
}
/**
* @en Dispatch an event.
* @param type The type of event.
* @param data (Optional) Data to pass to the callback. If multiple parameters p1, p2, p3, ... need to be passed, use an array structure such as [p1, p2, p3, ...]. If a single parameter p needs to be passed and p is an array, use a structure such as [p]. For other single parameters p, you can directly pass parameter p.
* @returns True if there are listeners for this event type, false otherwise.
* @zh 派发事件。
* @param type 事件类型。
* @param data (可选)回调数据。<b>注意:</b>如果是需要传递多个参数 p1,p2,p3,...可以使用数组结构如:[p1,p2,p3,...] ;如果需要回调单个参数 p ,且 p 是一个数组,则需要使用结构如:[p],其他的单个参数 p ,可以直接传入参数 p。
* @returns 此事件类型是否有侦听者,如果有侦听者则值为 true,否则值为 false。
*/
emit(type, data) {
let listeners = this._events && this._events[type];
if (!listeners)
return false;
let ret = listeners.count > 0;
if (Array.isArray(data))
listeners.invoke(...data);
else if (data !== undefined)
listeners.invoke(data);
else
listeners.invoke();
return ret;
}
on(type, caller, listener, args) {
if (arguments.length == 2) {
listener = caller;
caller = null;
}
if (!this._events)
this._events = {};
let listeners = this._events[type];
if (!listeners) {
this.onStartListeningToType(type);
this._events[type] = listeners = new Delegate();
}
listeners.add(listener, caller, args);
return this;
}
once(type, caller, listener, args) {
if (arguments.length == 2) {
listener = caller;
caller = null;
}
if (!this._events)
this._events = {};
let listeners = this._events[type];
if (!listeners) {
this.onStartListeningToType(type);
this._events[type] = listeners = new Delegate();
}
listeners.once(listener, caller, args);
return this;
}
off(type, caller, listener) {
if (arguments.length == 2) {
listener = caller;
caller = null;
}
let listeners = this._events && this._events[type];
if (listeners)
listeners.remove(listener, caller);
return this;
}
/**
* @en Remove all listeners of the specified event type from the EventDispatcher object.
* @param type (Optional) The type of event. If the value is null, all types of listeners on this object are removed.
* @returns This EventDispatcher object.
* @zh 从 EventDispatcher 对象中删除指定事件类型的所有侦听器。
* @param type (可选)事件类型,如果值为 null,则移除本对象所有类型的侦听器。
* @returns 此 EventDispatcher 对象。
*/
offAll(type) {
if (type == null)
this._events = undefined;
else {
let listeners = this._events && this._events[type];
if (listeners)
listeners.clear();
}
return this;
}
/**
* @en Remove all event listeners whose caller is the specified target.
* @param caller The target caller object.
* @returns This EventDispatcher object.
* @zh 移除caller为target的所有事件监听。
* @param caller caller对象
* @returns 此 EventDispatcher 对象。
*/
offAllCaller(caller) {
if (caller && this._events) {
for (let type in this._events)
this._events[type].clearForTarget(caller);
}
return this;
}
}
// 状态历史管理器
class StateHistoryManager {
constructor() {
this.history = [];
}
addEntry(entry) {
this.history.push(entry);
}
getHistory() {
return this.history;
}
print() {
console.log(JSON.stringify(this.history, null, 2));
}
clear() {
this.history = [];
}
exportToJson() {
return JSON.stringify(this.history);
}
}
// types.ts - 包含所有类型定义
/**
* StateMachine使用示例:
*
* 1. 创建状态机:
* const fsm = new StateMachine("playerFSM", {
* initialState: "idle"
* });
*
* 2. 创建状态:
* class IdleState extends BaseState {
* onEnter() {
* console.log("进入待机状态");
* }
* onExit() {
* console.log("退出待机状态");
* }
* }
*
* const idleState = new IdleState("idle");
*
* 3. 添加状态到状态机:
* fsm.addState(idleState);
*
* 4. 添加状态转换:
* fsm.addTransition("attack", "attacking",
* [() => player.canAttack()], // 守卫条件
* [() => player.playAttackAnim()] // 转换动作
* );
*
* 5. 处理输入:
* fsm.handleInput("attack");
*
* 6. 在游戏循环中更新:
* fsm.onUpdate(deltaTime);
*
* 7. 调试:
* fsm.enableDebug(); // 开启调试
* StateMachine.historyManager.print(); // 打印状态历史
*/
// 状态机实现
class StateMachine {
constructor(name, config, debugMode = false) {
this.name = name;
this.transitions = new Map();
this.states = new Map();
this.currentState = null;
this.previousState = null;
this.debugMode = false;
this.initialStateName = config.initialState;
this.debugMode = debugMode;
}
addState(state) {
if (this.states.has(state.name)) {
console.warn(`State ${state.name} already exists in ${this.name}`);
return;
}
state.parent = this;
this.states.set(state.name, state);
if (state.name === this.initialStateName && !this.currentState) {
this.changeState(state.name);
}
}
reset() {
if (this.currentState) {
this.currentState.onExit();
}
this.changeState(this.initialStateName);
}
getCurrentState() {
return this.currentState;
}
onEnter() {
if (!this.currentState && this.initialStateName) {
this.changeState(this.initialStateName);
}
}
onExit() {
if (this.currentState) {
this.currentState.onExit();
this.currentState = null;
}
}
onUpdate(deltaTime) {
if (this.currentState) {
this.currentState.onUpdate(deltaTime);
}
}
addTransition(input, from, to, guards = [], actions = [], priority = 0) {
const guardHandlers = guards.map((guard) => Handler.create(guard[0], guard[1], null, false));
const actionHandlers = actions.map((action) => Handler.create(action[0], action[1], null, false));
const rules = this.transitions.get(input) || [];
rules.push({ from: from, to: to, guards: guardHandlers, actions: actionHandlers, priority });
rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
this.transitions.set(input, rules);
}
getNextState(input, args = []) {
var _a, _b;
if (!this.currentState) {
return { success: false, failureReason: `No current state found` };
}
let rules = this.transitions.get(input);
rules = rules ? rules.filter((rule) => (this.currentState && rule.from === this.currentState.name)) : [];
if (rules.length === 0) {
return { success: false, failureReason: `No transition rules found for input: ${input} current state ${this.currentState.name}` };
}
for (const rule of rules) {
const guardsPassed = (_b = (_a = rule.guards) === null || _a === void 0 ? void 0 : _a.every((guard) => guard.runWith([...args, rule]))) !== null && _b !== void 0 ? _b : true;
if (guardsPassed) {
// rule.actions?.forEach((action) => action.runWith(args));
return {
success: true,
targetState: rule.to,
rule: rule,
};
}
}
return {
success: false,
failureReason: "Guard conditions not met",
};
}
handleInput(input, args = []) {
var _a;
if (!this.currentState) {
console.warn(`No current state found for input: ${input}`);
return false;
}
const transitionResult = this.getNextState(input, args);
if (transitionResult.success && transitionResult.targetState) {
this.changeState(transitionResult.targetState, input, args);
if (transitionResult.rule) {
(_a = transitionResult.rule.actions) === null || _a === void 0 ? void 0 : _a.forEach((action) => action.runWith(args));
}
return true;
}
else {
console.warn(`Failed to handle input: ${input}, reason: ${transitionResult.failureReason}`);
}
return false;
}
enableDebug() {
this.debugMode = true;
}
disableDebug() {
this.debugMode = false;
}
changeState(stateName, input, args = []) {
var _a;
const newState = this.states.get(stateName);
if (!newState) {
console.error(`State ${stateName} not found in ${this.name}`);
return;
}
const previousStateName = ((_a = this.currentState) === null || _a === void 0 ? void 0 : _a.name) || null;
if (this.currentState) {
this.currentState.onExit(...args);
this.previousState = this.currentState;
}
this.currentState = newState;
this.currentState.onEnter(...args);
console.warn(`State change from ${previousStateName} to ${stateName}`);
if (this.debugMode) {
StateMachine.historyManager.addEntry({
timestamp: Date.now(),
stateMachine: this.name,
from: previousStateName,
to: stateName,
input,
metadata: {
hierarchyLevel: this.getHierarchyLevel(),
hasParent: !!this.parent,
activeStates: this.getActiveStatesHierarchy(),
},
});
}
}
getHierarchyLevel() {
let level = 0;
let current = this;
while (current.parent) {
level++;
current = current.parent;
}
return level;
}
getActiveStatesHierarchy() {
const hierarchy = [];
let current = this;
while (current) {
if (current.currentState) {
hierarchy.unshift(current.currentState.name);
}
current = current.parent;
}
return hierarchy;
}
static getHistory() {
return this.historyManager.getHistory();
}
static printHistory() {
this.historyManager.print();
}
static clearHistory() {
this.historyManager.clear();
}
static exportHistory() {
return this.historyManager.exportToJson();
}
}
StateMachine.historyManager = new StateHistoryManager();
// 状态基类
class State {
constructor(name) {
this.name = name;
}
onExit(...args) { }
onEnter(...args) { }
onUpdate(...args) { }
}
class CommonUtil {
/**
* !#zh 拷贝object。
*/
/**
* 深度拷贝
* @param {any} sObj 拷贝的对象
* @returns
*/
static clone(sObj) {
if (sObj === null || typeof sObj !== "object") {
return sObj;
}
let s = {};
if (sObj.constructor === Array) {
s = [];
}
for (let i in sObj) {
if (sObj.hasOwnProperty(i)) {
s[i] = this.clone(sObj[i]);
}
}
return s;
}
/**
* 将object转化为数组
* @param { any} srcObj
* @returns
*/
static objectToArray(srcObj) {
let resultArr = [];
// 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
*/
static arrayToObject(srcObj, objectKey) {
let resultObj = {};
// 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
*/
static getWeightRandIndex(weightArr, totalWeight) {
let randWeight = Math.floor(Math.random() * totalWeight);
let sum = 0;
for (var weightIndex = 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 获取数列
*/
static getRandomNFromM(n, m) {
let array = [];
let intRd = 0;
let count = 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
*/
static getRandomInt(min, max) {
let r = Math.random();
let rr = r * (max - min + 1) + min;
return Math.floor(rr);
}
/**
* 获取随机数
* @param {Number} min 最小值
* @param {Number} max 最大值
* @returns
*/
static getRandom(min, max) {
return Math.random() * (max - min) + min;
}
/**
* 获取字符串长度
* @param {string} render
* @returns
*/
static getStringLength(render) {
let strArr = render;
let len = 0;
for (let i = 0, n = strArr.length; i < n; i++) {
let val = strArr.charCodeAt(i);
if (val <= 255) {
len = len + 1;
}
else {
len = len + 2;
}
}
return Math.ceil(len / 2);
}
/**
* 判断传入的参数是否为空的Object。数组或undefined会返回false
* @param obj
*/
static isEmptyObject(obj) {
let result = 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}
*/
static isNewDay(dateValue) {
// todo:是否需要判断时区?
var oldDate = new Date(dateValue);
var curDate = new Date();
//@ts-ignore
var oldYear = oldDate.getYear();
var oldMonth = oldDate.getMonth();
var oldDay = oldDate.getDate();
//@ts-ignore
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
*/
static getPropertyCount(o) {
var n, count = 0;
for (n in o) {
if (o.hasOwnProperty(n)) {
count++;
}
}
return count;
}
/**
* 返回一个差异化数组(将array中diff里的值去掉)
* @param array
* @param diff
*/
static difference(array, diff) {
let result = [];
if (array.constructor !== Array || diff.constructor !== Array) {
return result;
}
let length = array.length;
for (let i = 0; i < length; i++) {
if (diff.indexOf(array[i]) === -1) {
result.push(array[i]);
}
}
return result;
}
static _stringToArray(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) {
return reHasUnicode.test(val);
};
var unicodeToArray = function (val) {
return val.match(reUnicode) || [];
};
var asciiToArray = function (val) {
return val.split("");
};
return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
}
// 模拟传msg的uuid
static simulationUUID() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
}
static trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, "");
}
/**
* 判断当前时间是否在有效时间内
* @param {String|Number} start 起始时间。带有时区信息
* @param {String|Number} end 结束时间。带有时区信息
*/
static isNowValid(start, end) {
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
*/
static getDeltaDays(start, end) {
start = new Date(start);
end = new Date(end);
let startYear = start.getFullYear();
let startMonth = start.getMonth() + 1;
let startDate = start.getDate();
let endYear = end.getFullYear();
let endMonth = end.getMonth() + 1;
let endDate = 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
*/
static getMin(array) {
let result = 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
*/
static formatTwoDigits(time) {
//@ts-ignore
return (Array(2).join(0) + time).slice(-2);
}
/**
* 根据格式返回时间
* @param timestamp 时间
* @param format 格式
* @returns
*/
static formatDate(timestamp, format = "Y年M月D日 H时I分S秒") {
// 将时间戳转换为 Date 对象
const date = new Date(timestamp);
// 获取年、月、日、时、分、秒
const year = date.getFullYear();
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');
// 格式化输出
return format
.replace('Y', year.toString())
.replace('M', month)
.replace('D', day)
.replace('H', hours)
.replace('I', minutes)
.replace('S', seconds);
}
/**
* 根据格式返回时间2
* @param leftTime 剩余时间毫秒
* @param fmt 格式
* @returns
*/
static formatLeftTime(leftTime) {
// 将毫秒转换为秒
const totalSeconds = Math.floor(leftTime / 1000);
// 计算小时、分钟和秒
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
// 格式化输出,确保两位数
const pad = (num) => num.toString().padStart(2, '0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
// 传入一个时间整数,返回字符串。格式为如果小于一天hh:mm:ss, 否则返回dd:hh:mm;
static formatTime(time, isMillisecond = true, format = "D:H:M", format2 = "H:M:S") {
time = isMillisecond ? Math.floor(time / 1000) : time;
const day = Math.floor(time / 86400);
const hour = Math.floor((time % 86400) / 3600).toString().padStart(2, '0');
const minute = Math.floor((time % 3600) / 60).toString().padStart(2, '0');
const second = (time % 60).toString().padStart(2, '0');
if (day > 0) {
return format
.replace('D', day.toString())
.replace('H', hour)
.replace('M', minute);
}
return format2
.replace('H', hour)
.replace('M', minute)
.replace('S', second);
}
/**
* 获取格式化后的日期(不含小时分秒)
*/
static getDay() {
let date = new Date();
return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
}
/**
* 格式化名字,XXX...
* @param {string} name 需要格式化的字符串
* @param {number}limit
* @returns {string} 返回格式化后的字符串XXX...
*/
static formatName(name, limit) {
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}返回 被格式化的数值
*/
static formatMoney(money) {
let 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;
}
/**
* 格式化钱数,美分转换为美元
* @param {number}usdPercent 需要被格式化的数值
* @returns {string}返回 被格式化的数值
*/
static usd(usdPercent) {
return usdPercent * 0.01;
}
/**
* 格式化字符信息(基于数组)
* @param format
* @param args
* @param argsLang 参数是否转多语言,默认不转
*/
static formatArr(format, args, argsLang = false) {
if (!args)
return format;
let str = "";
let len = args.length;
for (let i = 0; i < len; i++) {
str = args[i];
if (str == null) {
str = "";
}
else {
str = str.toString();
}
format = format.replace(new RegExp("\\{" + i + "\\}", "g"), str);
}
format = format.replace(new RegExp("\\{" + "#" + "\\}", "g"), str);
return format;
}
static getErrorCode(key) {
// @ts-ignore
return this.errorCode[key] && (key = this.errorCode[key]);
}
/**
* 格式化数值
* @param {number}value 需要被格式化的数值
* @returns {string}返回 被格式化的数值
*/
static formatValue(value) {
let arrUnit = [];
let strValue = "";
for (let i = 0; i < 26; i++) {
arrUnit.push(String.fromCharCode(97 + i));
}
for (let idx = 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
*/
static formatTimeForSecond(leftSec, withoutSeconds = false) {
let timeStr = "";
let sec = leftSec % 60;
let leftMin = Math.floor(leftSec / 60);
leftMin = leftMin < 0 ? 0 : leftMin;
let hour = Math.floor(leftMin / 60);
let min = 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;
}
/**
* 根据剩余毫秒数格式化剩余时间 返回 HH:MM:SS
*
* @param {Number} ms
*/
static formatTimeForMillisecond(ms) {
let second = Math.floor((ms / 1000) % 60);
let minute = Math.floor((ms / 1000 / 60) % 60);
let hour = Math.floor(ms / 1000 / 60 / 60);
return { hour: hour, minute: minute, second: second };
}
/**
* 将数组内容进行随机排列
* @param {Array}arr 需要被随机的数组
* @returns
*/
static rand(arr) {
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;
}
/**
* 获得开始和结束两者之间相隔分钟数
*
* @static
* @param {number} start
* @param {number} end
* @memberof Util
*/
static getOffsetMimutes(start, end) {
let offSetTime = end - start;
let minute = Math.floor((offSetTime % (1000 * 60 * 60)) / (1000 * 60));
return minute;
}
/**
* 返回指定小数位的数值
* @param {number} num
* @param {number} idx
*/
static formatNumToFixed(num, idx = 0) {
return Number(num.toFixed(idx));
}
/**
* 用于数值到达另外一个目标数值之间进行平滑过渡运动效果
* @param {number} targetValue 目标数值
* @param {number} curValue 当前数值
* @param {number} ratio 过渡比率
* @returns
*/
static lerp(targetValue, curValue, ratio = 0.25) {
let v = curValue;
if (targetValue > curValue) {
v = curValue + (targetValue - curValue) * ratio;
}
else if (targetValue < curValue) {
v = curValue - (curValue - targetValue) * ratio;
}
return v;
}
/**
* 数据解密
* @param {String} str
*/
static decrypt(b64Data) {
let n = 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
*/
static encrypt(str) {
let b64Data = this._base64encode(str);
let n = 6;
if (b64Data.length % 2 === 0) {
n = 7;
}
let encodeData = "";
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
*/
static _base64encode(input) {
let keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let output = "", chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 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
*/
static _utf8Encode(string) {
string = string.replace(/\r\n/g, "\n");
let utftext = "";
for (let n = 0; n < string.length; n++) {
let c = 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
*/
static _utf8Decode(utftext) {
let string = "";
let i = 0;
let c = 0;
let c2 = 0;
let c3 = 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
*/
static _base64Decode(input) {
let keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let output = "";
let chr1;
let chr2;
let chr3;
let enc1;
let enc2;
let enc3;
let enc4;
let i = 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;
}
/**
* 洗牌函数
*
* @static
* @param {*} arr
* @returns
* @memberof util
*/
static shuffle(arr) {
if (Array.isArray(arr)) {
let newArr = arr.concat();
newArr.sort(() => {
return 0.5 - Math.random();
});
return newArr;
}
}
/**
* 两个数值数组取相同的值,返回一个新数组
*
* @static
* @param {number[]} arr1
* @param {number[]} arr2
* @returns
* @memberof util
*/
static filterDifferentValue(arr1, arr2) {
let arr = [];
arr = arr1.filter((item) => {
return arr2.indexOf(item) !== -1;
});
return arr;
}
/**
* 获取性能等级
* -Android
* 设备性能等级,取值为:
* -2 或 0(该设备无法运行小游戏)
* -1(性能未知)
* >=1(设备性能值,该值越高,设备性能越好,目前最高不到50)
* -IOS
* 微信不支持IO性能等级
* iPhone 5s 及以下
* 设定为超低端机 benchmarkLevel = 5
* iPhone 6 ~ iPhone SE
* 设定为超低端机 benchmarkLevel = 15
* iPhone 7 ~ iPhone X
* 设定为中端机 benchmarkLevel = 25
* iPhone XS 及以上
* 设定为高端机 benchmarkLevel = 40
* -H5或其他
* -1(性能未知)
*/
/**
* 获取当前机型性能是否为低端机
*/
static checkIsLowPhone() {
// @ts-ignore
if (window.wx) {
// 微信性能数值参考:https://developers.weixin.qq.com/minigame/dev/guide/performance/perf-benchmarkLevel.html
let nowBenchmarkLevel = -1; // nowBenchmarkLevel = -1性能未知
// @ts-ignore
const sys = window.wx.getSystemInfoSync();
const isIOS = sys.system.indexOf("iOS") >= 0;
if (isIOS) {
// 微信不支持IO性能等级
const model = sys.model;
// iPhone 5s 及以下 设定为超低端机
const ultraLowPhoneType = [
"iPhone1,1",
"iPhone1,2",
"iPhone2,1",
"iPhone3,1",
"iPhone3,3",
"iPhone4,1",
"iPhone5,1",
"iPhone5,2",
"iPhone5,3",
"iPhone5,4",
"iPhone6,1",
"iPhone6,2",
];
// iPhone 6 ~ iPhone SE 设定为超低端机
const lowPhoneType = ["iPhone6,2", "iPhone7,1", "iPhone7,2", "iPhone8,1", "iPhone8,2", "iPhone8,4"];
// iPhone 7 ~ iPhone X 设定为中端机
const middlePhoneType = [
"iPhone9,1",
"iPhone9,2",
"iPhone9,3",
"iPhone9,4",
"iPhone10,1",
"iPhone10,2",
"iPhone10,3",
"iPhone10,4",
"iPhone10,5",
"iPhone