Files
oops-plugin-framework/assets/core/common/timer/TimerManager.ts
dgflash d4f140077e feat (core): 完善框架核心功能模块 - 音频、对象池、资源加载与组件系统
新增与优化核心模块
🎯 GUI 交互增强
ButtonInterceptor:按钮点击事件劫持器
劫持 EventHandler.emitEvents / ButtonSimple.onClick 核心方法
支持多类型按钮音效配置注册,按按钮类型自动匹配音效
单例模式管理,支持全局动态激活 / 停用拦截功能
🧩 对象池管理系统
GameNodePool:通用游戏节点对象池管理器
基于预制体(Prefab)UUID 实现池化隔离管理
支持特效、UI 元素等高频对象预加载、复用与回收
全局单例访问,统一管控对象创建 / 销毁逻辑
🔊 音频管理系统
AudioManager:音频核心管理器
独立背景音乐(AudioMusic)+ 音效池(AudioEffectPool)双模块
支持全局音量控制、播放状态恢复与场景切换管理
AudioEffect:基础音效播放器
AudioEnum:音频类型、音效分类枚举定义
📦 资源加载系统
ResLoader:资源加载核心控制器
资源包(Bundle)统一管理 + 并发加载限流控制
远程资源本地缓存、自动释放与内存优化
完整加载进度回调 + 错误异常处理机制
ResTypes:资源类型规范定义
ResErrors:资源加载错误码与异常处理
ResUtils:资源路径、格式转换等工具方法
🧱 模块化组件系统
GameComponent:游戏显示对象组件基类
集成资源自动引用计数(RC)管理
模块化部件(GamePartRegistry)插拔式架构
内置音频、按钮、事件、键盘、节点、对象池、资源七大部件
部件封装:
GamePartAudio:音频功能部件
GamePartButton:按钮交互部件
GamePartEvent:全局事件部件
GamePartKeyboard:键盘输入部件
GamePartNode:节点操作部件
GamePartNodePool:对象池调用部件
GamePartRes:资源加载部件
📝 类型定义完善
新增 IAudio 音频数据与播放参数接口
统一全模块 TS 类型定义与导出规范
优化接口注释与类型约束,提升开发体验
2026-05-29 22:55:13 +08:00

397 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Component, game } from 'cc';
import { StringUtil } from '../../utils/StringUtil';
import { Timer } from './Timer';
/** 定时器数据接口 */
interface ITimer<T = Record<string, number>> {
/** 倒计时编号 */
id: string;
/** 定时器 */
timer: Timer;
/** 数据对象 - 必须包含数字类型的字段 */
object: any;
/** 修改数据对象的字段 */
field: keyof T;
/** 事件侦听器的目标和被叫方 */
target: object;
/** 开始时间 */
startTime: number;
/** 每秒触发事件 */
onSeconds: Function[] | null;
/** 时间完成事件 */
onCompletes: Function[] | null;
}
/** 时间管理 */
export class TimerManager extends Component {
/** 倒计时数据 - 使用 Map 提高性能 */
private times: Map<string, ITimer<Record<string, number>>> = new Map();
/** 服务器时间 - 复用对象减少 GC */
private date_s: Date = new Date();
/** 服务器初始时间 */
private date_s_start: Date = new Date();
/** 服务器时间后修正时间 */
private polymeric_s = 0;
/** 客户端时间 - 复用对象减少 GC */
private date_c: Date = new Date();
/** 待删除的定时器 ID 缓存池,避免遍历时删除 */
private pendingRemove: string[] = [];
/** ITimer 对象池,减少对象创建开销 */
private timerPool: ITimer<Record<string, number>>[] = [];
/** 后台管理倒计时完成事件 */
protected update(dt: number): void {
// 清空待删除列表
this.pendingRemove.length = 0;
// 使用 for...of 遍历 Map.values(),性能优于 forEach
for (const data of this.times.values()) {
const timer = data.timer;
if (timer.update(dt)) {
const value = data.object[data.field];
if (value > 0) {
data.object[data.field] = value - 1;
const newValue = data.object[data.field];
// 倒计时结束触发
if (newValue === 0) {
this.pendingRemove.push(data.id);
this.onTimerComplete(data);
}
// 触发每秒回调事件
else if (data.onSeconds && data.onSeconds.length > 0) {
// 使用 for 循环替代 forEach减少函数调用开销
const callbacks = data.onSeconds;
const len = callbacks.length;
for (let i = 0; i < len; i++) {
callbacks[i].call(data.object);
}
}
}
}
}
// 延迟删除已完成的定时器,避免遍历时修改 Map
if (this.pendingRemove.length > 0) {
for (let i = 0; i < this.pendingRemove.length; i++) {
this.times.delete(this.pendingRemove[i]);
}
}
}
/** 触发倒计时完成事件 */
private onTimerComplete(data: ITimer<Record<string, number>>): void {
if (data.onCompletes && data.onCompletes.length > 0) {
// 使用 for 循环替代 forEach减少函数调用开销
const callbacks = data.onCompletes;
const len = callbacks.length;
for (let i = 0; i < len; i++) {
callbacks[i].call(data.target, data.object);
}
}
// 清理内存
this.cleanupTimer(data);
}
/** 清理定时器相关引用,防止内存泄漏 */
private cleanupTimer(data: ITimer<Record<string, number>>): void {
if (data.timer) {
data.timer.destroy();
data.timer = null!;
}
// 清空回调数组并回收到对象池
if (data.onSeconds) {
data.onSeconds.length = 0;
data.onSeconds = null;
}
if (data.onCompletes) {
data.onCompletes.length = 0;
data.onCompletes = null;
}
// 清空引用
data.object = null!;
data.target = null!;
// 回收 ITimer 对象到对象池(限制池大小避免内存浪费)
if (this.timerPool.length < 50) {
this.timerPool.push(data);
}
}
/** 从对象池获取或创建新的 ITimer 对象 */
private acquireTimer<T extends Record<string, number>>(): ITimer<T> {
if (this.timerPool.length > 0) {
// 从对象池获取时需要类型断言,因为池中的对象会被重新赋值
return this.timerPool.pop() as unknown as ITimer<T>;
}
// 创建新对象
return {
id: '',
timer: null!,
object: null!,
field: '' as keyof T,
target: null!,
startTime: 0,
onSeconds: null,
onCompletes: null,
};
}
/**
* 在指定对象上注册一个倒计时的回调管理器
* @template T 数据对象类型,必须包含数字类型的字段
* @param object 注册定时器的对象(必须包含可数字递减的字段)
* @param field 时间字段名(必须是 object 中数字类型的字段)
* @param target 触发事件的对象
* @param onSecond 每秒事件回调
* @param onComplete 倒计时完成事件回调
* @returns 倒计时编号
* @example
export class Test extends Component {
private timeId!: string;
private data = { countDown: 10 };
start() {
// 在指定对象上注册一个倒计时的回调管理器
this.timeId = oops.timer.register(this.data, "countDown", this, this.onSecond, this.onComplete);
}
private onSecond() {
console.log("每秒触发一次");
}
private onComplete() {
console.log("倒计时完成触发");
}
}
*/
register<T extends Record<string, number>>(
object: object,
field: keyof T,
target: object,
onSecond?: Function,
onComplete?: Function
): string {
const timer = new Timer();
timer.step = 1;
// 从对象池获取 ITimer 对象
const data = this.acquireTimer<T>();
data.id = StringUtil.guid();
data.timer = timer;
data.object = object;
data.field = field;
data.target = target;
data.startTime = this.getTime();
// 只在需要时创建数组,减少内存分配
if (onSecond) {
data.onSeconds = data.onSeconds || [];
data.onSeconds.push(onSecond);
} else {
data.onSeconds = null;
}
if (onComplete) {
data.onCompletes = data.onCompletes || [];
data.onCompletes.push(onComplete);
} else {
data.onCompletes = null;
}
// 类型断言:将泛型类型转换为通用类型存储
this.times.set(data.id, data as unknown as ITimer<Record<string, number>>);
return data.id;
}
/**
* 为指定倒计时添加回调事件
* @param id 倒计时编号
* @param onSecond 每秒事件
* @param onComplete 倒计时完成事件
*/
addCallback(id: string, onSecond?: Function, onComplete?: Function): void {
const data = this.times.get(id);
if (data) {
// 检查回调是否已存在,避免重复添加
if (onSecond) {
if (!data.onSeconds) {
data.onSeconds = [];
}
if (!data.onSeconds.includes(onSecond)) {
data.onSeconds.push(onSecond);
}
}
if (onComplete) {
if (!data.onCompletes) {
data.onCompletes = [];
}
if (!data.onCompletes.includes(onComplete)) {
data.onCompletes.push(onComplete);
}
}
}
}
/**
* 移除指定倒计时的回调事件
* @param id 倒计时编号
* @param onSecond 要移除的每秒事件
* @param onComplete 要移除的倒计时完成事件
*/
removeCallback(id: string, onSecond?: Function, onComplete?: Function): void {
const data = this.times.get(id);
if (data) {
if (onSecond && data.onSeconds) {
const index = data.onSeconds.indexOf(onSecond);
if (index > -1) {
data.onSeconds.splice(index, 1);
}
}
if (onComplete && data.onCompletes) {
const index = data.onCompletes.indexOf(onComplete);
if (index > -1) {
data.onCompletes.splice(index, 1);
}
}
}
}
/**
* 在指定对象上注销一个倒计时的回调管理器
* @param id 时间对象唯一表示
* @example
export class Test extends Component {
private timeId!: string;
start() {
this.timeId = oops.timer.register(this, "countDown", this.onSecond, this.onComplete);
}
onDestroy() {
// 在指定对象上注销一个倒计时的回调管理器
oops.timer.unRegister(this.timeId);
}
}
*/
unRegister(id: string): void {
const data = this.times.get(id);
if (data) {
this.cleanupTimer(data);
this.times.delete(id);
}
}
/**
* 检查指定 id 的定时器是否存在
* @param id 倒计时编号
*/
has(id: string): boolean {
return this.times.has(id);
}
/**
* 获取当前活跃的定时器数量
*/
getTimerCount(): number {
return this.times.size;
}
/**
* 服务器时间与本地时间同步
* @param value 服务器时间刻度
*/
setServerTime(value: number): void {
this.polymeric_s = this.getTime();
this.date_s_start.setTime(value);
}
/** 获取写服务器同步的时间刻度 */
getServerTime(): number {
return this.date_s_start.getTime() + this.getTime() - this.polymeric_s;
}
/** 获取服务器时间对象 */
getServerDate(): Date {
this.date_s.setTime(this.getServerTime());
return this.date_s;
}
/** 获取本地时间刻度 */
getClientTime(): number {
return Date.now();
}
/** 获取本地时间对象 */
getClientDate(): Date {
this.date_c.setTime(this.getClientTime());
return this.date_c;
}
/** 获取游戏开始到现在逝去的时间 */
getTime(): number {
return game.totalTime;
}
/** 游戏最小化时记录时间数据 */
save(): void {
const currentTime = this.getTime();
// 使用 for...of 替代 forEach提高性能
for (const data of this.times.values()) {
data.startTime = currentTime;
}
}
/** 游戏最大化时恢复时间数据 */
load(): void {
const currentTime = this.getTime();
// 清空待删除列表
this.pendingRemove.length = 0;
// 使用 for...of 替代 forEach提高性能
for (const data of this.times.values()) {
const interval = Math.floor((currentTime - (data.startTime || currentTime)) / 1000);
const currentValue = data.object[data.field];
data.object[data.field] = currentValue - interval;
if (data.object[data.field] <= 0) {
data.object[data.field] = 0;
this.pendingRemove.push(data.id);
this.onTimerComplete(data);
}
}
// 延迟删除已完成的定时器
if (this.pendingRemove.length > 0) {
for (let i = 0; i < this.pendingRemove.length; i++) {
this.times.delete(this.pendingRemove[i]);
}
}
}
/**
* 清理所有定时器,释放内存
* 注意:此方法会清除所有正在运行的定时器
*/
clear(): void {
// 使用 for...of 替代 forEach提高性能
for (const data of this.times.values()) {
this.cleanupTimer(data);
}
this.times.clear();
// 清空待删除列表
this.pendingRemove.length = 0;
}
/**
* 组件销毁时清理所有资源
*/
protected onDestroy(): void {
this.clear();
// 清理对象池
this.timerPool.length = 0;
}
}