From b0938a9fb8237897c2105e24a49dd871dbcfa0e8 Mon Sep 17 00:00:00 2001 From: janing Date: Sun, 14 Dec 2025 22:40:38 +0800 Subject: [PATCH] =?UTF-8?q?Framework.UI=E7=AE=80=E5=8D=95UI=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/assets/scripts/Framework/Net.meta | 9 + client/assets/scripts/Framework/ResMgr.meta | 9 + client/assets/scripts/Framework/UI.meta | 9 + client/assets/scripts/Framework/UI/README.md | 447 ++++++++++++++++++ client/assets/scripts/Framework/UI/UIBase.ts | 124 +++++ .../scripts/Framework/UI/UIBase.ts.meta | 9 + client/assets/scripts/Framework/UI/UIMgr.ts | 293 ++++++++++++ .../assets/scripts/Framework/UI/UIMgr.ts.meta | 9 + .../scripts/Framework/UI/UIMgrExample.ts | 247 ++++++++++ .../scripts/Framework/UI/UIMgrExample.ts.meta | 9 + 10 files changed, 1165 insertions(+) create mode 100644 client/assets/scripts/Framework/Net.meta create mode 100644 client/assets/scripts/Framework/ResMgr.meta create mode 100644 client/assets/scripts/Framework/UI.meta create mode 100644 client/assets/scripts/Framework/UI/README.md create mode 100644 client/assets/scripts/Framework/UI/UIBase.ts create mode 100644 client/assets/scripts/Framework/UI/UIBase.ts.meta create mode 100644 client/assets/scripts/Framework/UI/UIMgr.ts create mode 100644 client/assets/scripts/Framework/UI/UIMgr.ts.meta create mode 100644 client/assets/scripts/Framework/UI/UIMgrExample.ts create mode 100644 client/assets/scripts/Framework/UI/UIMgrExample.ts.meta diff --git a/client/assets/scripts/Framework/Net.meta b/client/assets/scripts/Framework/Net.meta new file mode 100644 index 0000000..cf1bdd7 --- /dev/null +++ b/client/assets/scripts/Framework/Net.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "9fc0bab1-69b0-4f45-9f40-e4711ee0a541", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/ResMgr.meta b/client/assets/scripts/Framework/ResMgr.meta new file mode 100644 index 0000000..39cf82e --- /dev/null +++ b/client/assets/scripts/Framework/ResMgr.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "6196366f-2afd-4ade-a6b3-7a675ed9a56f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/UI.meta b/client/assets/scripts/Framework/UI.meta new file mode 100644 index 0000000..d1c49b1 --- /dev/null +++ b/client/assets/scripts/Framework/UI.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "f6e8a8b0-3c5d-4e7f-9d2a-8f5c6d4e7a9b", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/UI/README.md b/client/assets/scripts/Framework/UI/README.md new file mode 100644 index 0000000..fd26406 --- /dev/null +++ b/client/assets/scripts/Framework/UI/README.md @@ -0,0 +1,447 @@ +# UI 系统 (Framework/UI) + +## 📋 模块概述 +统一的 UI 管理系统,管理 UI 的加载、显示、隐藏和生命周期,提供 UI 基类和管理器,支持缓存和自动资源管理。 + +## 🎯 核心特性 +- ✅ UI 基类定义 +- ✅ UI 生命周期管理 +- ✅ UI 缓存机制 +- ✅ 自动资源管理 +- ✅ UI 更新机制 +- ✅ 类型安全 + +## 🗂️ 文件结构 + +``` +Framework/UI/ +├── UIBase.ts # UI 基类 +├── UIMgr.ts # UI 管理器 +└── UIMgrExample.ts # 使用示例 +``` + +## 📘 核心类详解 + +### UIBase - UI 基类 + +**职责**: 定义 UI 的基础接口和生命周期 + +```typescript +abstract class UIBase { + protected _node: Node | null; // UI 根节点 + protected _isLoaded: boolean; // 是否已加载 + protected _isShowing: boolean; // 是否显示中 + + // 必须重载: 获取 UI 资源路径 + abstract onGetUrl(): string; + + // 可选重载: UI 初始化(加载完成后) + onStart?(params?: any): void | Promise; + + // 可选重载: UI 清理(卸载前) + onEnd?(): void; + + // 可选重载: 每帧更新 + onUpdate?(dt: number): void; + + // 设置 UI 根节点 + setNode(node: Node): void; + + // 获取 UI 根节点 + getNode(): Node | null; + + // 显示 UI + show(): void; + + // 隐藏 UI + hide(): void; + + // 检查是否显示中 + isShowing(): boolean; + + // 检查是否已加载 + isLoaded(): boolean; +} +``` + +### UIMgr - UI 管理器 + +**职责**: 管理 UI 的加载、卸载和生命周期 + +```typescript +class UIMgr { + private _uiCache: Map; // UI 缓存 + private _uiRoot: Node | null; // UI 根节点 + private _updateList: UIBase[]; // 需要更新的 UI 列表 + + // 获取单例 + static getInstance(): UIMgr; + + // 设置 UI 根节点(Canvas) + setUIRoot(root: Node): void; + + // 加载并显示 UI + async load( + uiClass: new () => T, + params?: any + ): Promise; + + // 卸载 UI + unload(uiClass: (new () => UIBase) | string): void; + + // 获取已加载的 UI 实例 + get(uiClass: (new () => T) | string): T | null; + + // 检查 UI 是否已加载 + has(uiClass: (new () => UIBase) | string): boolean; + + // 更新所有需要更新的 UI + update(dt: number): void; + + // 卸载所有 UI + unloadAll(): void; + + // 销毁管理器 + destroy(): void; +} +``` + +## 📝 使用指南 + +### 1. 创建自定义 UI + +```typescript +import { _decorator } from 'cc'; +import { UIBase } from './Framework/UI/UIBase'; + +const { ccclass } = _decorator; + +@ccclass('UILogin') +export class UILogin extends UIBase { + private _btnLogin: Node | null = null; + private _inputAccount: EditBox | null = null; + + /** + * 必须重载: 返回 UI 资源路径 + */ + onGetUrl(): string { + return 'prefabs/ui/UILogin'; + } + + /** + * 可选重载: UI 初始化 + */ + async onStart(params?: any): Promise { + console.log('[UILogin] 初始化', params); + + // 获取节点 + const node = this.getNode(); + if (node) { + this._btnLogin = node.getChildByName('BtnLogin'); + this._inputAccount = node.getChildByName('InputAccount') + ?.getComponent(EditBox) || null; + } + + // 绑定事件 + if (this._btnLogin) { + this._btnLogin.on(Node.EventType.TOUCH_END, this.onClickLogin, this); + } + } + + /** + * 可选重载: UI 清理 + */ + onEnd(): void { + console.log('[UILogin] 清理'); + + // 解绑事件 + if (this._btnLogin) { + this._btnLogin.off(Node.EventType.TOUCH_END, this.onClickLogin, this); + } + } + + /** + * 可选重载: 每帧更新 + */ + onUpdate(dt: number): void { + // 更新倒计时等逻辑 + } + + private onClickLogin(): void { + console.log('[UILogin] 点击登录'); + // 处理登录逻辑 + } +} +``` + +### 2. 使用 UIMgr 管理 UI + +```typescript +import { find } from 'cc'; +import { UIMgr } from './Framework/UI/UIMgr'; +import { UILogin } from './UI/UILogin'; + +// 1. 设置 UI 根节点(通常在启动时设置一次) +const canvas = find('Canvas'); +if (canvas) { + UIMgr.getInstance().setUIRoot(canvas); +} + +// 2. 加载并显示 UI +const loginUI = await UIMgr.getInstance().load(UILogin); + +// 3. 传递参数给 UI +const loginUI = await UIMgr.getInstance().load(UILogin, { + username: 'test' +}); + +// 4. 获取已加载的 UI 实例 +const ui = UIMgr.getInstance().get(UILogin); +if (ui) { + ui.hide(); // 隐藏 + ui.show(); // 显示 +} + +// 5. 检查 UI 是否已加载 +if (UIMgr.getInstance().has(UILogin)) { + console.log('UILogin 已加载'); +} + +// 6. 卸载 UI +UIMgr.getInstance().unload(UILogin); +// 或使用类名 +UIMgr.getInstance().unload('UILogin'); + +// 7. 卸载所有 UI(场景切换时) +UIMgr.getInstance().unloadAll(); +``` + +### 3. 在游戏主循环中更新 UI + +```typescript +import { Component, _decorator } from 'cc'; +import { UIMgr } from './Framework/UI/UIMgr'; + +const { ccclass } = _decorator; + +@ccclass('GameManager') +export class GameManager extends Component { + update(deltaTime: number) { + // 更新所有 UI + UIMgr.getInstance().update(deltaTime); + } +} +``` + +### 4. UI 状态切换示例 + +```typescript +// UI 管理器封装 +class UIController { + async showLogin(): Promise { + // 隐藏其他 UI + UIMgr.getInstance().unload(UIMain); + + // 显示登录 UI + await UIMgr.getInstance().load(UILogin); + } + + async showMain(): Promise { + // 隐藏登录 UI + UIMgr.getInstance().unload(UILogin); + + // 显示主界面 + await UIMgr.getInstance().load(UIMain); + } + + async showBattle(): Promise { + // 主界面保持显示,加载战斗 UI + await UIMgr.getInstance().load(UIBattle); + } +} +``` + +## 🔄 UI 加载流程 + +``` +UIMgr.load(UIClass, params) + ↓ +1. 检查缓存 + ├─ 如果已缓存 + │ ├─ 调用 ui.show() + │ ├─ 调用 ui.onStart(params) + │ └─ 返回缓存实例 + └─ 如果未缓存 + ↓ +2. 创建 UI 实例 + ↓ +3. 调用 ui.onGetUrl() 获取资源路径 + ↓ +4. 通过 ResMgr 加载预制体 + ↓ +5. 实例化预制体节点 + ↓ +6. 添加到 UI 根节点 + ↓ +7. 调用 ui.setNode(node) + ↓ +8. 缓存 UI 实例 + ↓ +9. 如果有 onUpdate,添加到更新列表 + ↓ +10. 调用 ui.onStart(params) + ↓ +11. 调用 ui.show() + ↓ +12. 返回 UI 实例 +``` + +## 📊 UI 生命周期 + +``` +[创建 UI 类实例] + ↓ +onGetUrl() - 获取资源路径(必须) + ↓ +[加载预制体] + ↓ +setNode(node) - 设置节点 + ↓ +onStart(params) - 初始化 UI(可选) + ↓ +show() - 显示 UI + ↓ +onUpdate(dt) - 每帧更新(可选) + ↓ +[卸载 UI] + ↓ +onEnd() - 清理资源(可选) + ↓ +[销毁节点和释放资源] +``` + +## ⚠️ 注意事项 + +1. **必须重载 onGetUrl()**: 每个 UI 必须明确定义资源路径 +2. **UI 根节点设置**: 在加载任何 UI 之前,必须先设置 UI 根节点 +3. **缓存机制**: 已加载的 UI 会被缓存,再次加载时直接使用缓存 +4. **资源自动管理**: UIMgr 会自动管理资源加载和释放 +5. **异步加载**: load 方法是异步的,需要使用 await +6. **类型安全**: 使用泛型确保返回正确的 UI 类型 + +## 🔍 调试技巧 + +### 日志输出 + +```typescript +// UIMgr 内部包含详细日志: +// [UIMgr] 设置UI根节点 +// [UIMgr] 开始加载UI: UILogin +// [UIMgr] 从缓存加载UI: UILogin +// [UIMgr] UI加载完成: UILogin +// [UIMgr] 卸载UI: UILogin +``` + +### 检查 UI 状态 + +```typescript +const ui = UIMgr.getInstance().get(UILogin); +if (ui) { + console.log(`UI已加载: ${ui.isLoaded()}`); + console.log(`UI显示中: ${ui.isShowing()}`); +} +``` + +### 常见问题 + +**问题1**: UI 根节点未设置 +```typescript +// 解决: 在加载 UI 前设置根节点 +const canvas = find('Canvas'); +if (canvas) { + UIMgr.getInstance().setUIRoot(canvas); +} +``` + +**问题2**: UI 未正确显示 +```typescript +// 检查: 资源路径是否正确 +onGetUrl(): string { + return 'prefabs/ui/UILogin'; // 确保路径正确 +} +``` + +**问题3**: UI 重复加载 +```typescript +// 利用缓存机制: +if (!UIMgr.getInstance().has(UILogin)) { + await UIMgr.getInstance().load(UILogin); +} +``` + +## 💡 最佳实践 + +1. **单一职责**: 每个 UI 类只负责一个界面 +2. **资源路径统一**: 建议在配置文件中统一管理 UI 资源路径 +3. **事件解绑**: 在 onEnd() 中解绑所有事件,避免内存泄漏 +4. **参数传递**: 使用 params 参数在加载时传递初始数据 +5. **缓存利用**: 对频繁切换的 UI,利用缓存避免重复加载 +6. **及时卸载**: 不再使用的 UI 及时卸载,释放资源 + +## 🎯 应用场景 + +- ✅ 登录界面 +- ✅ 主界面 +- ✅ 战斗界面 +- ✅ 设置界面 +- ✅ 背包界面 +- ✅ 商店界面 +- ✅ 对话框 +- ✅ 提示框 + +## 📚 扩展功能 + +### UI 层级管理 + +```typescript +// 可以扩展 UIMgr 支持 UI 层级 +enum UILayer { + Background = 0, // 背景层 + Normal = 100, // 普通层 + Popup = 200, // 弹窗层 + Top = 300 // 顶层 +} + +// 在加载 UI 时指定层级 +ui.getNode()?.setSiblingIndex(UILayer.Popup); +``` + +### UI 动画 + +```typescript +// 在 onStart 中添加打开动画 +async onStart(): Promise { + const node = this.getNode(); + if (node) { + // 缩放动画 + node.scale = Vec3.ZERO; + tween(node) + .to(0.3, { scale: Vec3.ONE }, { easing: 'backOut' }) + .start(); + } +} + +// 在关闭时添加动画 +async close(): Promise { + const node = this.getNode(); + if (node) { + await new Promise(resolve => { + tween(node) + .to(0.2, { scale: Vec3.ZERO }) + .call(resolve) + .start(); + }); + } + UIMgr.getInstance().unload(this.constructor as any); +} +``` diff --git a/client/assets/scripts/Framework/UI/UIBase.ts b/client/assets/scripts/Framework/UI/UIBase.ts new file mode 100644 index 0000000..13a272b --- /dev/null +++ b/client/assets/scripts/Framework/UI/UIBase.ts @@ -0,0 +1,124 @@ +import { Node } from 'cc'; + +/** + * UI基类 + * 职责: + * - 定义UI的基础接口和生命周期 + * - 管理UI根节点 + * - 提供显示/隐藏控制 + * + * 所有UI必须继承此类并实现onGetUrl方法 + */ +export abstract class UIBase { + /** UI根节点 */ + protected _node: Node | null = null; + + /** UI是否已加载 */ + protected _isLoaded: boolean = false; + + /** UI是否显示中 */ + protected _isShowing: boolean = false; + + /** + * 获取UI资源路径 (必须重载) + * @returns UI预制体路径 + * @example + * onGetUrl(): string { + * return 'UI/Login/UILogin'; + * } + */ + abstract onGetUrl(): string; + + /** + * UI开始时调用 (可选重载) + * 在UI预制体加载完成后调用 + * 可以在此方法中初始化UI数据、绑定事件等 + * @param params 传入的参数 + * @example + * onStart(params?: any): void { + * console.log('UI初始化', params); + * // 绑定按钮事件 + * this.bindEvents(); + * } + */ + onStart?(params?: any): void | Promise; + + /** + * UI结束时调用 (可选重载) + * 在UI被卸载前调用 + * 可以在此方法中清理资源、解绑事件等 + * @example + * onEnd(): void { + * console.log('UI清理'); + * // 解绑按钮事件 + * this.unbindEvents(); + * } + */ + onEnd?(): void; + + /** + * UI更新 (可选重载) + * 在每帧调用(仅当UI显示时) + * @param dt 距离上一帧的时间(秒) + * @example + * onUpdate(dt: number): void { + * // 更新倒计时 + * this.updateTimer(dt); + * } + */ + onUpdate?(dt: number): void; + + /** + * 设置UI根节点 + * 由UIMgr在加载完成后调用 + * @param node UI根节点 + */ + setNode(node: Node): void { + this._node = node; + this._isLoaded = true; + } + + /** + * 获取UI根节点 + * @returns UI根节点,如果未加载则返回null + */ + getNode(): Node | null { + return this._node; + } + + /** + * 显示UI + */ + show(): void { + if (this._node) { + this._node.active = true; + this._isShowing = true; + } + } + + /** + * 隐藏UI + */ + hide(): void { + if (this._node) { + this._node.active = false; + this._isShowing = false; + } + } + + /** + * 检查UI是否显示中 + * @returns true表示正在显示,false表示已隐藏 + */ + isShowing(): boolean { + return this._isShowing; + } + + /** + * 检查UI是否已加载 + * @returns true表示已加载,false表示未加载 + */ + isLoaded(): boolean { + return this._isLoaded; + } +} diff --git a/client/assets/scripts/Framework/UI/UIBase.ts.meta b/client/assets/scripts/Framework/UI/UIBase.ts.meta new file mode 100644 index 0000000..3e2aab5 --- /dev/null +++ b/client/assets/scripts/Framework/UI/UIBase.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "0a812118-97d6-470b-b73d-933bc31fb3d4", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/UI/UIMgr.ts b/client/assets/scripts/Framework/UI/UIMgr.ts new file mode 100644 index 0000000..a47dc42 --- /dev/null +++ b/client/assets/scripts/Framework/UI/UIMgr.ts @@ -0,0 +1,293 @@ +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 = 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( + uiClass: new () => T, + params?: any + ): Promise { + 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})`); + + // 通过ResMgr加载预制体 + try { + const prefab = await ResMgr.getInstance().load( + 'resources', + url, + 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(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}`); + } + } +} diff --git a/client/assets/scripts/Framework/UI/UIMgr.ts.meta b/client/assets/scripts/Framework/UI/UIMgr.ts.meta new file mode 100644 index 0000000..545ddeb --- /dev/null +++ b/client/assets/scripts/Framework/UI/UIMgr.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "bb73ed34-9565-47f3-8ec9-e10367ef52f4", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/UI/UIMgrExample.ts b/client/assets/scripts/Framework/UI/UIMgrExample.ts new file mode 100644 index 0000000..7daed42 --- /dev/null +++ b/client/assets/scripts/Framework/UI/UIMgrExample.ts @@ -0,0 +1,247 @@ +import { _decorator, Component, Node, Label, Button, find } from 'cc'; +import { UIBase } from './UIBase'; +import { UIMgr } from './UIMgr'; + +const { ccclass, property } = _decorator; + +/** + * UI使用示例 + * + * 本文件展示如何使用UI系统: + * 1. 创建自定义UI类 + * 2. 加载和显示UI + * 3. 更新UI + * 4. 卸载UI + */ + +// ============================================ +// 示例1: 简单的UI类 +// ============================================ + +/** + * 登录UI示例 + */ +export class UILogin extends UIBase { + /** + * 返回UI预制体路径 + */ + onGetUrl(): string { + return 'UI/Login/UILogin'; + } + + /** + * UI初始化 + */ + onStart(params?: any): void { + console.log('[UILogin] 初始化', params); + + // 获取节点 + const node = this.getNode(); + if (!node) return; + + // 查找按钮并绑定事件 + const btnLogin = node.getChildByName('BtnLogin'); + if (btnLogin) { + const button = btnLogin.getComponent(Button); + if (button) { + button.node.on(Button.EventType.CLICK, this.onLoginClick, this); + } + } + } + + /** + * UI清理 + */ + onEnd(): void { + console.log('[UILogin] 清理'); + + // 解绑事件 + const node = this.getNode(); + if (!node) return; + + const btnLogin = node.getChildByName('BtnLogin'); + if (btnLogin) { + btnLogin.off(Button.EventType.CLICK, this.onLoginClick, this); + } + } + + /** + * 登录按钮点击 + */ + private onLoginClick(): void { + console.log('[UILogin] 点击登录按钮'); + // 执行登录逻辑... + } +} + +// ============================================ +// 示例2: 带更新的UI类 +// ============================================ + +/** + * 游戏主界面UI示例 + * 包含倒计时等需要每帧更新的逻辑 + */ +export class UIMain extends UIBase { + private _timeLeft: number = 60; + private _labelTime: Label | null = null; + + onGetUrl(): string { + return 'UI/Main/UIMain'; + } + + onStart(): void { + console.log('[UIMain] 初始化'); + + const node = this.getNode(); + if (!node) return; + + // 获取时间Label + const timeNode = node.getChildByName('LabelTime'); + if (timeNode) { + this._labelTime = timeNode.getComponent(Label); + } + + this.updateTimeDisplay(); + } + + /** + * 每帧更新 + */ + onUpdate(dt: number): void { + // 更新倒计时 + this._timeLeft -= dt; + if (this._timeLeft < 0) { + this._timeLeft = 0; + } + + this.updateTimeDisplay(); + } + + /** + * 更新时间显示 + */ + private updateTimeDisplay(): void { + if (this._labelTime) { + this._labelTime.string = `剩余时间: ${Math.floor(this._timeLeft)}s`; + } + } +} + +// ============================================ +// 示例3: 在Component中使用UI系统 +// ============================================ + +/** + * UI管理器使用示例组件 + */ +@ccclass('UIMgrExample') +export class UIMgrExample extends Component { + + start() { + // 设置UI根节点 + const canvas = find('Canvas'); + if (canvas) { + UIMgr.getInstance().setUIRoot(canvas); + } + + // 演示UI加载和使用 + this.testUISystem(); + } + + update(dt: number) { + // 每帧更新UI系统 + UIMgr.getInstance().update(dt); + } + + /** + * 测试UI系统 + */ + private async testUISystem() { + console.log('========== UI系统测试开始 =========='); + + try { + // 1. 加载登录UI + console.log('\n--- 测试1: 加载登录UI ---'); + const loginUI = await UIMgr.getInstance().load(UILogin, { + username: 'player1' + }); + console.log('登录UI加载成功:', loginUI); + + // 2. 检查UI是否存在 + console.log('\n--- 测试2: 检查UI状态 ---'); + console.log('是否已加载:', UIMgr.getInstance().has(UILogin)); + console.log('是否显示中:', loginUI.isShowing()); + + // 3. 隐藏和显示UI + console.log('\n--- 测试3: 隐藏和显示 ---'); + setTimeout(() => { + console.log('隐藏登录UI'); + UIMgr.getInstance().hide(UILogin); + }, 2000); + + setTimeout(() => { + console.log('显示登录UI'); + UIMgr.getInstance().show(UILogin); + }, 4000); + + // 4. 加载主界面UI + console.log('\n--- 测试4: 加载主界面UI ---'); + setTimeout(async () => { + const mainUI = await UIMgr.getInstance().load(UIMain); + console.log('主界面UI加载成功:', mainUI); + console.log('当前UI数量:', UIMgr.getInstance().getUICount()); + }, 6000); + + // 5. 卸载UI + console.log('\n--- 测试5: 卸载UI ---'); + setTimeout(() => { + console.log('卸载登录UI'); + UIMgr.getInstance().unload(UILogin); + console.log('当前UI数量:', UIMgr.getInstance().getUICount()); + }, 8000); + + // 6. 卸载所有UI + console.log('\n--- 测试6: 卸载所有UI ---'); + setTimeout(() => { + console.log('卸载所有UI'); + UIMgr.getInstance().unloadAll(); + console.log('当前UI数量:', UIMgr.getInstance().getUICount()); + }, 10000); + + } catch (error) { + console.error('UI系统测试失败:', error); + } + + console.log('\n========== UI系统测试完成 =========='); + } +} + +// ============================================ +// 使用说明 +// ============================================ + +/** + * 基本使用流程: + * + * 1. 创建UI类: + * export class MyUI extends UIBase { + * onGetUrl(): string { + * return 'UI/MyUI'; + * } + * } + * + * 2. 设置UI根节点: + * UIMgr.getInstance().setUIRoot(canvas); + * + * 3. 加载并显示UI: + * const ui = await UIMgr.getInstance().load(MyUI); + * + * 4. 在主循环中更新: + * update(dt: number) { + * UIMgr.getInstance().update(dt); + * } + * + * 5. 卸载UI: + * UIMgr.getInstance().unload(MyUI); + */ diff --git a/client/assets/scripts/Framework/UI/UIMgrExample.ts.meta b/client/assets/scripts/Framework/UI/UIMgrExample.ts.meta new file mode 100644 index 0000000..9fa6254 --- /dev/null +++ b/client/assets/scripts/Framework/UI/UIMgrExample.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "d8fecb48-938f-489a-83e5-510c8247bc09", + "files": [], + "subMetas": {}, + "userData": {} +}