Framework.UI简单UI管理类
This commit is contained in:
9
client/assets/scripts/Framework/Net.meta
Normal file
9
client/assets/scripts/Framework/Net.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "9fc0bab1-69b0-4f45-9f40-e4711ee0a541",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
client/assets/scripts/Framework/ResMgr.meta
Normal file
9
client/assets/scripts/Framework/ResMgr.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "6196366f-2afd-4ade-a6b3-7a675ed9a56f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
client/assets/scripts/Framework/UI.meta
Normal file
9
client/assets/scripts/Framework/UI.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "f6e8a8b0-3c5d-4e7f-9d2a-8f5c6d4e7a9b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
447
client/assets/scripts/Framework/UI/README.md
Normal file
447
client/assets/scripts/Framework/UI/README.md
Normal file
@@ -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<void>;
|
||||
|
||||
// 可选重载: 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<string, UIBase>; // UI 缓存
|
||||
private _uiRoot: Node | null; // UI 根节点
|
||||
private _updateList: UIBase[]; // 需要更新的 UI 列表
|
||||
|
||||
// 获取单例
|
||||
static getInstance(): UIMgr;
|
||||
|
||||
// 设置 UI 根节点(Canvas)
|
||||
setUIRoot(root: Node): void;
|
||||
|
||||
// 加载并显示 UI
|
||||
async load<T extends UIBase>(
|
||||
uiClass: new () => T,
|
||||
params?: any
|
||||
): Promise<T>;
|
||||
|
||||
// 卸载 UI
|
||||
unload(uiClass: (new () => UIBase) | string): void;
|
||||
|
||||
// 获取已加载的 UI 实例
|
||||
get<T extends UIBase>(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<void> {
|
||||
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<void> {
|
||||
// 隐藏其他 UI
|
||||
UIMgr.getInstance().unload(UIMain);
|
||||
|
||||
// 显示登录 UI
|
||||
await UIMgr.getInstance().load(UILogin);
|
||||
}
|
||||
|
||||
async showMain(): Promise<void> {
|
||||
// 隐藏登录 UI
|
||||
UIMgr.getInstance().unload(UILogin);
|
||||
|
||||
// 显示主界面
|
||||
await UIMgr.getInstance().load(UIMain);
|
||||
}
|
||||
|
||||
async showBattle(): Promise<void> {
|
||||
// 主界面保持显示,加载战斗 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<void> {
|
||||
const node = this.getNode();
|
||||
if (node) {
|
||||
// 缩放动画
|
||||
node.scale = Vec3.ZERO;
|
||||
tween(node)
|
||||
.to(0.3, { scale: Vec3.ONE }, { easing: 'backOut' })
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
// 在关闭时添加动画
|
||||
async close(): Promise<void> {
|
||||
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);
|
||||
}
|
||||
```
|
||||
124
client/assets/scripts/Framework/UI/UIBase.ts
Normal file
124
client/assets/scripts/Framework/UI/UIBase.ts
Normal file
@@ -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<void>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
9
client/assets/scripts/Framework/UI/UIBase.ts.meta
Normal file
9
client/assets/scripts/Framework/UI/UIBase.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "0a812118-97d6-470b-b73d-933bc31fb3d4",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
293
client/assets/scripts/Framework/UI/UIMgr.ts
Normal file
293
client/assets/scripts/Framework/UI/UIMgr.ts
Normal file
@@ -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<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})`);
|
||||
|
||||
// 通过ResMgr加载预制体
|
||||
try {
|
||||
const prefab = await ResMgr.getInstance().load<Prefab>(
|
||||
'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<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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
client/assets/scripts/Framework/UI/UIMgr.ts.meta
Normal file
9
client/assets/scripts/Framework/UI/UIMgr.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "bb73ed34-9565-47f3-8ec9-e10367ef52f4",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
247
client/assets/scripts/Framework/UI/UIMgrExample.ts
Normal file
247
client/assets/scripts/Framework/UI/UIMgrExample.ts
Normal file
@@ -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);
|
||||
*/
|
||||
9
client/assets/scripts/Framework/UI/UIMgrExample.ts.meta
Normal file
9
client/assets/scripts/Framework/UI/UIMgrExample.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d8fecb48-938f-489a-83e5-510c8247bc09",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user