挂机养成类游戏中战斗加速的需求非常普遍
需要加速的不光有帧动画 骨骼动画 还有音频 缓动 定时器等等
但是并没有一个接口来统一控制上述系统的时间间隔
下面直接上代码
*只做了简单测试 另外写的时候不是很清醒 如有错误请轻喷
import Manager from "./Manager";
/**时间管理类 @description 用于控制时间流速 */
class TimeMgr extends Manager {
private _speed: number = 1;
/**全局时间流速 */
public get speed(): number {
return this._speed;
}
public set speed(newValue: number) {
this._speed = newValue;
//同步全局时间流速
this.speed_audio = this._speed;
this.speed_skeleton = this._speed;
this.speed_action = this._speed;
this.speed_animation = this._speed;
this.speed_collision = this._speed;
this.speed_physics = this._speed;
this.speed_timer = this._speed;
}
private _speed_audio: number = 1;
/**音频时间流速 */
public get speed_audio(): number {
return this._speed_audio;
}
public set speed_audio(newValue: number) {
this._speed_audio = newValue;
hh.audioMgr.musicSource.audio._element._currentSource.playbackRate.value = this._speed_audio;
hh.audioMgr.effectSources.forEach(effectSource => {
if (effectSource.audio._element) effectSource.audio._element._currentSource.playbackRate.value = this._speed_audio;
});
}
private _speed_skeleton: number = 1;
/**骨骼时间流速 */
public get speed_skeleton(): number {
return this._speed_skeleton;
}
public set speed_skeleton(newValue: number) {
this._speed_skeleton = newValue;
sp.timeScale = this._speed_skeleton;
dragonBones.timeScale = this._speed_skeleton;
}
private _speed_action: number = 1;
/**动作时间流速 */
public get speed_action(): number {
return this._speed_action;
}
public set speed_action(newValue: number) {
this._speed_action = newValue;
}
private _speed_animation: number = 1;
/**动画时间流速 */
public get speed_animation(): number {
return this._speed_animation;
}
public set speed_animation(newValue: number) {
this._speed_animation = newValue;
}
private _speed_collision: number = 1;
/**碰撞时间流速 */
public get speed_collision(): number {
return this._speed_collision;
}
public set speed_collision(newValue: number) {
this._speed_collision = newValue;
}
private _speed_physics: number = 1;
/**物理时间流速 */
public get speed_physics(): number {
return this._speed_physics;
}
public set speed_physics(newValue: number) {
this._speed_physics = newValue;
}
private _speed_physics3D: number = 1;
/**3D物理时间流速 */
public get speed_physics3D(): number {
return this._speed_physics3D;
}
public set speed_physics3D(newValue: number) {
this._speed_physics3D = newValue;
}
private _speed_timer: number = 1;
/**定时器时间流速 */
public get speed_timer(): number {
return this._speed_timer;
}
public set speed_timer(newValue: number) {
this._speed_timer = newValue;
cc.director._scheduler._timeScale = this._speed_timer;
}
private _timers: Map<Timer['tag'], Timer[]> = null;
/**定时器任务标签索引存放对象 */
private get timersByTag(): Map<Timer['tag'], Timer[]> {
if (this._timers === null) {
this._timers = new Map();
}
return this._timers;
}
private _timersByID: Map<Timer['id'], Timer> = null;
/**定时器任务ID索引存放对象 */
private get timersByID(): Map<Timer['id'], Timer> {
if (this._timersByID === null) {
this._timersByID = new Map();
}
return this._timersByID;
}
protected init() {
//hook动作间隔帧时间的计算
const { value: update_action } = Reflect.getOwnPropertyDescriptor(cc.ActionManager.prototype, 'update');
Reflect.defineProperty(cc.director._actionManager, 'update', {
value: function (dt: number) {
update_action.call(this, dt * hh.timeMgr.speed_action / hh.timeMgr.speed_timer);
},
});
//hook动画间隔帧时间的计算
const { value: update_animation } = Reflect.getOwnPropertyDescriptor(cc.AnimationManager.prototype, 'update');
Reflect.defineProperty(cc.director._animationManager, 'update', {
value: function (dt: number) {
update_animation.call(this, dt * hh.timeMgr.speed_animation / hh.timeMgr.speed_timer);
},
});
//hook碰撞间隔帧时间的计算
const { value: update_collision } = Reflect.getOwnPropertyDescriptor(cc.CollisionManager.prototype, 'update');
Reflect.defineProperty(cc.director._collisionManager, 'update', {
value: function (dt: number) {
update_collision.call(this, dt * hh.timeMgr.speed_collision / hh.timeMgr.speed_timer);
},
});
//hook物理间隔帧时间的计算
const { value: update_physics } = Reflect.getOwnPropertyDescriptor(cc.PhysicsManager.prototype, 'update');
Reflect.defineProperty(cc.director._physicsManager, 'update', {
value: function (dt: number) {
update_physics.call(this, dt * hh.timeMgr.speed_physics / hh.timeMgr.speed_timer);
},
});
//hook物理间隔帧时间的计算
const { value: update_physics3D } = Reflect.getOwnPropertyDescriptor(cc.Physics3DManager.prototype, 'update');
Reflect.defineProperty(cc.director._physics3DManager, 'update', {
value: function (dt: number) {
update_physics3D.call(this, dt * hh.timeMgr.speed_physics3D / hh.timeMgr.speed_timer);
},
});
}
/**
* 调度定时器超时任务方法
* @param cb 定时器回调函数
* @param target 定时器回调对象
* @param delay 延迟触发时间(单位ms)
* @example //回调函数传参
* let cb: Function = function (num1: number, num2: number): number { return this.add(num1, num2) }.bind(this, 1, 2);
* hh.timeMgr.setTimeout(cb, this, 3);
*/
public setTimeout(cb: Timer['cb'], target: Timer['target'], delay: number): Timer['id'] {
//执行定时器
let timerID: Timer['id'] = this.setTimer(cb, target, { tag: 'timeout', interval: 0, repeat: 1, delay: delay });
//清理定时器
for (let timer of cc.director._scheduler._hashForTimers[this.timersByID.get(timerID)._id].timers) {
let cancel = timer.cancel;
Reflect.defineProperty(timer, 'cancel', {
value: function () {
cancel.call(this);
hh.timeMgr.clearTimer(timerID);
},
});
}
//返回定时器任务ID
return timerID;
}
/**
* 调度定时器间隔任务方法
* @param cb 定时器回调函数
* @param target 定时器回调对象
* @param interval 触发间隔时间(单位ms填0为每帧触发)
* @example //回调函数传参
* let cb: Function = function (num1: number, num2: number): number { return this.add(num1, num2) }.bind(this, 1, 2);
* hh.timeMgr.setInterval(cb, this, 3);
*/
public setInterval(cb: Timer['cb'], target: Timer['target'], interval: number): Timer['id'] {
//执行定时器
return this.setTimer(cb, target, { tag: 'interval', interval: interval, repeat: cc.macro.REPEAT_FOREVER, delay: interval });
}
/**
* 取消定时器超时任务方法
* @param id 定时器任务ID
*/
public clearTimeout(id: Timer['id']): boolean {
return this.clearTimer(id);
}
/**
* 取消定时器间隔任务方法
* @param id 定时器任务ID
*/
public clearInterval(id: Timer['id']): boolean {
return this.clearTimer(id);
}
/**
* 取消定时器任务方法
* @param id 定时器任务ID
*/
public clearTimer(id: Timer['id']): boolean {
//获取定时器
let timer: Timer = this.timersByID.get(id);
if (!timer) return false;
//取消定时器
cc.director._scheduler.unschedule(timer.cb, timer);
//清理定时器
this.timersByID.delete(timer.id);
hh.tools.removeItems(this.timersByTag.get(timer.tag), timer);
return true;
}
/**
* 调度定时器任务方法
* @param cb 定时器回调函数
* @param target 定时器回调对象
* @param opts 定时器参数对象
*/
public setTimer<T extends {
/**定时器任务标签(不填则默认为default) */
tag?: Timer['tag'],
/**触发间隔时间(单位ms不填则默认为0于每帧触发) */
interval?: number,
/**重复次数(不填则默认为一直持续触发) */
repeat?: number,
/**首次触发延迟时间(单位ms不填则默认为0于下帧触发) */
delay?: number,
}>(cb: Timer['cb'], target: Timer['target'], opts?: T): Timer['id'];
public setTimer(cb: Timer['cb'], target: any, interval?: number, repeat?: number, delay?: number): Timer['id'];
public setTimer<T extends { tag?: Timer['tag'], interval?: number, repeat?: number, delay?: number }>(cb: Timer['cb'], target: Timer['target'], opts: T | number = {} as any, ..._: any[]): Timer['id'] {
//解构参数对象
let tag: Timer['tag'],
interval: number,
repeat: number,
delay: number;
if (arguments.length === [cb, target].length || (arguments.length === [cb, target, opts].length && typeof opts === 'object')) {
tag = (opts as T)?.tag ?? 'default';
interval = (opts as T)?.interval / 1000 ?? 0;
repeat = (opts as T)?.repeat ?? cc.macro.REPEAT_FOREVER;
delay = (opts as T)?.delay / 1000 ?? 0;
}
else {
tag = 'default';
interval = arguments[2] / 1000 ?? 0;
repeat = arguments[3] ?? cc.macro.REPEAT_FOREVER;
delay = arguments[4] / 1000 ?? 0;
}
//创建定时器
let timer: Timer = { cb: cb, target: target, tag: tag, id: performance.now() };
//执行定时器
cc.director._scheduler.enableForTarget(timer);
if (interval === 0 && repeat === cc.macro.REPEAT_FOREVER && delay === 0) {
timer.update = cb as Timer['update'];
cc.director._scheduler.scheduleUpdate(timer, cc.Scheduler.PRIORITY_NON_SYSTEM, cc.director._scheduler.isTargetPaused(timer));
}
else cc.director._scheduler.schedule(cb, timer, interval, repeat - 1, delay === 0 ? cc.director._deltaTime : delay, cc.director._scheduler.isTargetPaused(timer));
//缓存定时器
this.timersByID.set(timer.id, timer);
if (!this.timersByTag.get(tag)?.length) this.timersByTag.set(tag, []);
this.timersByTag.get(tag).push(timer);
//返回定时器任务ID
return timer.id;
}
public correctTimerByID(id: Timer['id'], timeScale: number) {
for (let timer of cc.director._scheduler._hashForTimers[this.timersByID.get(id)._id].timers) {
timer._interval *= 1 / timeScale;
if (this.timersByID.get(id).tag === 'interval') timer._delay *= 1 / timeScale;
}
}
public correctTimersByTag(tag: Timer['tag'], timeScale: number) {
this.timersByTag.get(tag)?.forEach(_timer => {
for (let timer of cc.director._scheduler._hashForTimers[_timer._id].timers) {
timer._interval *= 1 / timeScale;
if (tag === 'interval') timer._delay *= 1 / timeScale;
}
});
}
/**
* 分帧执行方法
* @param process 处理函数(若存在返回值则作为是否中止执行的判断条件)
* @param opts 执行参数对象
* @param args 处理函数参数
*/
public async framing(process: (
/**当前执行次数索引 */
index: number,
) => any, opts: {
/**总执行次数 */
total: number,
/**单次执行次数(默认为1次) */
step?: number,
/**单次执行间隔(单位ms默认为100ms) */
interval?: number,
/**绑定处理函数的this指向对象 */
target?: any,
}) {
return new Promise<void>(async (resolve, reject) => {
//解构参数对象
let { total, step = 1, interval = 100, target } = opts ?? {};
//声明累计执行计数
let count: number = 0;
//执行分帧处理
_framing();
//声明分帧处理方法
async function _framing() {
//获取本次执行次数
let once: number = Math.min(total - count, step);
//循环执行处理函数
for (let i: number = 0; i < once; i++) {
//达成条件中止执行
if (await process.call(target, count)) {
reject(count);
return;
}
//统计当前执行次数
count++;
}
//继续执行剩余次数
if (count < total) setTimeout(_framing, interval);
else resolve();
}
});
}
/**
* 获取当前时间方法
* @param opts 日期格式参数对象
* @returns 返回当前指定日期格式时间文本字符串
*/
public getLocalDate<T extends keyof {
/**年-月-日 */
'YYYY_MM_DD',
/**时-分-秒 */
'HH_MM_SS',
}>(opts?: {
/**日期格式(默认为完整格式) */
format?: T,
/**世界时间(默认为本地时间) */
isWorld?: boolean,
}): string {
//解构参数对象
let { format, isWorld = false } = opts ?? {};
//获取当前时间
isWorld = isWorld ? 'UTC' : '' as any;
let time: Date = new Date();
let year: number = time[`get${isWorld}FullYear`](); //年
let month: number = time[`get${isWorld}Month`]() + 1; //月
let day: number = time[`get${isWorld}Date`](); //日
let hour: number = time[`get${isWorld}Hours`](); //时
let minute: number = time[`get${isWorld}Minutes`](); //分
let second: number = time[`get${isWorld}Seconds`](); //秒
let millisecond: number = time[`get${isWorld}Milliseconds`](); //毫秒
let week: string[] = ['日', '一', '二', '三', '四', '五', '六'];
let index: number = time[`get${isWorld}Day`](); //星期几
//格式化时分秒毫秒
hour = hour.toString().length === 1 ? '0' + hour : hour as any;
minute = minute.toString().length === 1 ? '0' + minute : minute as any;
second = second.toString().length === 1 ? '0' + second : second as any;
if (millisecond.toString().length === 1) millisecond = '00' + millisecond as any;
if (millisecond.toString().length === 2) millisecond = '0' + millisecond as any;
//返回当前时间
if (format === 'YYYY_MM_DD') return `${year}年${month}月${day}日`;
if (format === 'HH_MM_SS') return `${hour}时${minute}分${second}秒`;
return `${year}年${month}月${day}日\r\n${hour}时${minute}分${second}秒${millisecond}毫秒\r\n星期${week[index]}`;
}
/**
* 全局延时阻塞方法
* @param time 等待时间(单位ms默认为延迟一帧)
*/
public async wait(time: number = 0) {
return new Promise<void>(resolve => setTimeout(resolve, time));
}
}
/**定时器绑定目标 */
type Timer = {
/**定时器任务ID */
id: ReturnType<Performance['now']>,
/**定时器任务回调 */
cb: Parameters<cc.Scheduler['schedule']>[0],
/**定时器任务目标 */
target: Parameters<cc.Scheduler['schedule']>[1],
/**定时器任务标签 */
tag: 'default' | 'timeout' | 'interval',
/**定时器ID */
_id?: `Scheduler.${number}`,
/**定时器更新回调 */
update?: cc.Scheduler['update'],
};
export { TimeMgr as default };