Files
rougelike-demo/client/assets/scripts/Framework/UI/UIMgr.ts
2025-12-14 23:35:54 +08:00

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}`);
}
}
}