296 lines
7.9 KiB
TypeScript
296 lines
7.9 KiB
TypeScript
import { Node, Prefab, instantiate } from 'cc';
|
|
import { UIBase } from './UIBase';
|
|
import { ResMgr } from '../ResMgr/ResMgr';
|
|
|
|
/**
|
|
* UI管理器
|
|
* 职责:
|
|
* - 管理所有UI的加载、显示、隐藏和销毁
|
|
* - 维护UI缓存,避免重复加载
|
|
* - 管理UI的生命周期
|
|
* - 提供UI更新机制
|
|
*/
|
|
export class UIMgr {
|
|
private static _instance: UIMgr;
|
|
|
|
/** 已加载的UI实例缓存 */
|
|
private _uiCache: Map<string, UIBase> = new Map();
|
|
|
|
/** UI父节点(Canvas) */
|
|
private _uiRoot: Node | null = null;
|
|
|
|
/** 需要更新的UI列表 */
|
|
private _updateList: UIBase[] = [];
|
|
|
|
/**
|
|
* 获取单例
|
|
*/
|
|
static getInstance(): UIMgr {
|
|
if (!this._instance) {
|
|
this._instance = new UIMgr();
|
|
}
|
|
return this._instance;
|
|
}
|
|
|
|
/**
|
|
* 设置UI根节点
|
|
* @param root Canvas节点或UI容器节点
|
|
* @example
|
|
* const canvas = find('Canvas');
|
|
* if (canvas) {
|
|
* UIMgr.getInstance().setUIRoot(canvas);
|
|
* }
|
|
*/
|
|
setUIRoot(root: Node): void {
|
|
this._uiRoot = root;
|
|
console.log('[UIMgr] 设置UI根节点');
|
|
}
|
|
|
|
/**
|
|
* 加载并显示UI
|
|
* @param uiClass UI类
|
|
* @param params 传递给onStart的参数
|
|
* @returns UI实例
|
|
* @example
|
|
* const loginUI = await UIMgr.getInstance().load(UILogin, { username: 'test' });
|
|
*/
|
|
async load<T extends UIBase>(
|
|
uiClass: new () => T,
|
|
params?: any
|
|
): Promise<T> {
|
|
const className = uiClass.name;
|
|
|
|
// 检查缓存
|
|
let ui = this._uiCache.get(className) as T;
|
|
if (ui) {
|
|
console.log(`[UIMgr] 从缓存加载UI: ${className}`);
|
|
ui.show();
|
|
if (ui.onStart) {
|
|
await ui.onStart(params);
|
|
}
|
|
return ui;
|
|
}
|
|
|
|
// 创建UI实例
|
|
ui = new uiClass();
|
|
|
|
// 获取UI资源路径
|
|
const url = ui.onGetUrl();
|
|
if (!url) {
|
|
console.error(`[UIMgr] UI未定义资源路径: ${className}`);
|
|
throw new Error(`UI未定义资源路径: ${className}`);
|
|
}
|
|
|
|
console.log(`[UIMgr] 开始加载UI: ${className} (${url})`);
|
|
const args = url.split('://')
|
|
const bundleName = args[0];
|
|
const resourcePath = args[1];
|
|
// 通过ResMgr加载预制体
|
|
try {
|
|
const prefab = await ResMgr.getInstance().load<Prefab>(
|
|
bundleName,
|
|
resourcePath,
|
|
Prefab
|
|
);
|
|
|
|
// 实例化预制体
|
|
const node = instantiate(prefab);
|
|
|
|
// 添加到UI根节点
|
|
if (this._uiRoot) {
|
|
node.setParent(this._uiRoot);
|
|
} else {
|
|
console.warn(`[UIMgr] UI根节点未设置,UI将无父节点: ${className}`);
|
|
}
|
|
|
|
// 设置UI节点
|
|
ui.setNode(node);
|
|
|
|
// 缓存UI
|
|
this._uiCache.set(className, ui);
|
|
|
|
// 如果UI有onUpdate方法,添加到更新列表
|
|
if (ui.onUpdate) {
|
|
this._updateList.push(ui);
|
|
}
|
|
|
|
// 调用onStart生命周期
|
|
if (ui.onStart) {
|
|
await ui.onStart(params);
|
|
}
|
|
|
|
// 显示UI
|
|
ui.show();
|
|
|
|
console.log(`[UIMgr] UI加载完成: ${className}`);
|
|
return ui;
|
|
|
|
} catch (error) {
|
|
console.error(`[UIMgr] 加载UI失败: ${className}`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 卸载UI
|
|
* @param uiClass UI类或类名
|
|
* @example
|
|
* UIMgr.getInstance().unload(UILogin);
|
|
* // 或
|
|
* UIMgr.getInstance().unload('UILogin');
|
|
*/
|
|
unload(uiClass: (new () => UIBase) | string): void {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
const ui = this._uiCache.get(className);
|
|
if (!ui) {
|
|
console.warn(`[UIMgr] UI未加载,无法卸载: ${className}`);
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIMgr] 卸载UI: ${className}`);
|
|
|
|
// 调用onEnd生命周期
|
|
if (ui.onEnd) {
|
|
ui.onEnd();
|
|
}
|
|
|
|
// 从更新列表移除
|
|
const index = this._updateList.indexOf(ui);
|
|
if (index !== -1) {
|
|
this._updateList.splice(index, 1);
|
|
}
|
|
|
|
// 销毁节点
|
|
const node = ui.getNode();
|
|
if (node && node.isValid) {
|
|
node.destroy();
|
|
}
|
|
|
|
// 从缓存移除
|
|
this._uiCache.delete(className);
|
|
|
|
// 释放资源
|
|
const url = ui.onGetUrl();
|
|
if (url) {
|
|
ResMgr.getInstance().release('resources', url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取已加载的UI实例
|
|
* @param uiClass UI类或类名
|
|
* @returns UI实例,如果未加载则返回null
|
|
* @example
|
|
* const loginUI = UIMgr.getInstance().get(UILogin);
|
|
* if (loginUI) {
|
|
* loginUI.show();
|
|
* }
|
|
*/
|
|
get<T extends UIBase>(uiClass: (new () => T) | string): T | null {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
return (this._uiCache.get(className) as T) || null;
|
|
}
|
|
|
|
/**
|
|
* 检查UI是否已加载
|
|
* @param uiClass UI类或类名
|
|
* @returns true表示已加载,false表示未加载
|
|
* @example
|
|
* if (UIMgr.getInstance().has(UILogin)) {
|
|
* console.log('登录UI已加载');
|
|
* }
|
|
*/
|
|
has(uiClass: (new () => UIBase) | string): boolean {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
return this._uiCache.has(className);
|
|
}
|
|
|
|
/**
|
|
* 更新所有需要更新的UI
|
|
* 应在主循环中每帧调用
|
|
* @param dt 距离上一帧的时间(秒)
|
|
* @example
|
|
* // 在主场景的update方法中调用
|
|
* update(dt: number) {
|
|
* UIMgr.getInstance().update(dt);
|
|
* }
|
|
*/
|
|
update(dt: number): void {
|
|
for (const ui of this._updateList) {
|
|
if (ui.isShowing() && ui.onUpdate) {
|
|
ui.onUpdate(dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 卸载所有UI
|
|
* @example
|
|
* // 在场景切换或退出游戏时调用
|
|
* UIMgr.getInstance().unloadAll();
|
|
*/
|
|
unloadAll(): void {
|
|
console.log(`[UIMgr] 卸载所有UI`);
|
|
|
|
const classNames = Array.from(this._uiCache.keys());
|
|
for (const className of classNames) {
|
|
this.unload(className);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取当前已加载UI的数量
|
|
* @returns UI数量
|
|
*/
|
|
getUICount(): number {
|
|
return this._uiCache.size;
|
|
}
|
|
|
|
/**
|
|
* 显示指定UI(不重新加载)
|
|
* @param uiClass UI类或类名
|
|
* @example
|
|
* UIMgr.getInstance().show(UILogin);
|
|
*/
|
|
show(uiClass: (new () => UIBase) | string): void {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
const ui = this._uiCache.get(className);
|
|
if (ui) {
|
|
ui.show();
|
|
} else {
|
|
console.warn(`[UIMgr] UI未加载,无法显示: ${className}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 隐藏指定UI(不卸载)
|
|
* @param uiClass UI类或类名
|
|
* @example
|
|
* UIMgr.getInstance().hide(UILogin);
|
|
*/
|
|
hide(uiClass: (new () => UIBase) | string): void {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
const ui = this._uiCache.get(className);
|
|
if (ui) {
|
|
ui.hide();
|
|
} else {
|
|
console.warn(`[UIMgr] UI未加载,无法隐藏: ${className}`);
|
|
}
|
|
}
|
|
}
|