feat: 添加页面管理系统和首页/关卡页面
- 实现 ViewManager 单例页面管理器,支持页面注册、打开、关闭、缓存 - 实现 BaseView 页面基类,提供统一的页面生命周期 - 添加 PageHome 首页,包含开始游戏按钮跳转功能 - 添加 PageLevel 关卡页面,继承 BaseView - 更新 PageLoading 支持进度条显示和页面预加载 - 添加相关图片资源和预制体 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
9
assets/scripts/core.meta
Normal file
9
assets/scripts/core.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
131
assets/scripts/core/BaseView.ts
Normal file
131
assets/scripts/core/BaseView.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { _decorator, Component } from 'cc';
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/**
|
||||
* 页面配置接口
|
||||
*/
|
||||
export interface ViewConfig {
|
||||
prefabPath: string; // 相对于 resources 的路径
|
||||
cache?: boolean; // 是否缓存页面,默认 true
|
||||
zIndex?: number; // 层级,默认 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面打开选项
|
||||
*/
|
||||
export interface ViewOptions {
|
||||
params?: any; // 传递给页面的参数
|
||||
onComplete?: (view: BaseView) => void;
|
||||
onError?: (err: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面基类
|
||||
* 所有页面组件应继承此类,实现生命周期方法
|
||||
*/
|
||||
@ccclass('BaseView')
|
||||
export class BaseView extends Component {
|
||||
/** 页面唯一标识 */
|
||||
viewId: string = '';
|
||||
|
||||
/** 页面配置 */
|
||||
config: ViewConfig | null = null;
|
||||
|
||||
/** 是否正在显示 */
|
||||
isShowing: boolean = false;
|
||||
|
||||
/** 传递给页面的参数 */
|
||||
protected _params: any = null;
|
||||
|
||||
/**
|
||||
* 设置页面参数
|
||||
*/
|
||||
setParams(params: any): void {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面参数
|
||||
*/
|
||||
getParams(): any {
|
||||
return this._params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面加载时调用(首次创建时)
|
||||
* 子类应重写此方法
|
||||
*/
|
||||
onViewLoad(): void {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面显示时调用(每次打开时)
|
||||
* 子类应重写此方法
|
||||
*/
|
||||
onViewShow(): void {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面隐藏时调用(关闭或被其他页面覆盖时)
|
||||
* 子类应重写此方法
|
||||
*/
|
||||
onViewHide(): void {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面销毁时调用
|
||||
* 子类应重写此方法
|
||||
*/
|
||||
onViewDestroy(): void {
|
||||
// 子类实现
|
||||
}
|
||||
|
||||
// ========== 内部方法,由 ViewManager 调用 ==========
|
||||
|
||||
/**
|
||||
* 内部方法:执行显示逻辑
|
||||
*/
|
||||
_doShow(): void {
|
||||
if (this.isShowing) return;
|
||||
this.isShowing = true;
|
||||
this.node.active = true;
|
||||
this.onViewShow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法:执行隐藏逻辑
|
||||
*/
|
||||
_doHide(): void {
|
||||
if (!this.isShowing) return;
|
||||
this.isShowing = false;
|
||||
this.onViewHide();
|
||||
this.node.active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部方法:执行销毁逻辑
|
||||
*/
|
||||
_doDestroy(): void {
|
||||
// 标记已销毁,防止 onDestroy 中重复调用
|
||||
this._destroyed = true;
|
||||
this.node.destroy();
|
||||
}
|
||||
|
||||
/** 是否已标记销毁 */
|
||||
private _destroyed: boolean = false;
|
||||
|
||||
// ========== Cocos 生命周期 ==========
|
||||
|
||||
protected onDestroy(): void {
|
||||
// 仅在未被 _doDestroy 调用时执行生命周期
|
||||
if (!this._destroyed) {
|
||||
if (this.isShowing) {
|
||||
this.onViewHide();
|
||||
}
|
||||
}
|
||||
this.onViewDestroy();
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/BaseView.ts.meta
Normal file
9
assets/scripts/core/BaseView.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "b1c2d3e4-f5a6-7890-bcde-f12345678901",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
350
assets/scripts/core/ViewManager.ts
Normal file
350
assets/scripts/core/ViewManager.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
import { _decorator, Node, resources, Prefab, instantiate, error } from 'cc';
|
||||
import { BaseView, ViewConfig, ViewOptions } from './BaseView';
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/**
|
||||
* 已注册的页面配置映射
|
||||
*/
|
||||
interface RegisteredView {
|
||||
config: ViewConfig;
|
||||
prefab: Prefab | null; // 缓存的预制体
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面管理器
|
||||
* 单例模式,统一管理页面的注册、打开、关闭、返回等操作
|
||||
*/
|
||||
@ccclass('ViewManager')
|
||||
export class ViewManager {
|
||||
/** 单例实例 */
|
||||
private static _instance: ViewManager | null = null;
|
||||
|
||||
/** 获取单例 */
|
||||
public static get instance(): ViewManager {
|
||||
if (!ViewManager._instance) {
|
||||
ViewManager._instance = new ViewManager();
|
||||
}
|
||||
return ViewManager._instance;
|
||||
}
|
||||
|
||||
/** 页面容器节点 */
|
||||
private _container: Node | null = null;
|
||||
|
||||
/** 已注册的页面配置 */
|
||||
private _registeredViews: Map<string, RegisteredView> = new Map();
|
||||
|
||||
/** 页面栈 */
|
||||
private _viewStack: BaseView[] = [];
|
||||
|
||||
/** 页面实例缓存(用于缓存模式的页面) */
|
||||
private _viewCache: Map<string, BaseView> = new Map();
|
||||
|
||||
/**
|
||||
* 初始化管理器
|
||||
* @param container 页面容器节点(通常是 Canvas)
|
||||
*/
|
||||
init(container: Node): void {
|
||||
this._container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前容器
|
||||
*/
|
||||
getContainer(): Node | null {
|
||||
return this._container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册页面
|
||||
* @param viewId 页面唯一标识
|
||||
* @param config 页面配置
|
||||
*/
|
||||
register(viewId: string, config: ViewConfig): void {
|
||||
if (this._registeredViews.has(viewId)) {
|
||||
error(`ViewManager: 页面 "${viewId}" 已注册`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._registeredViews.set(viewId, {
|
||||
config: {
|
||||
cache: true, // 默认缓存
|
||||
zIndex: 0, // 默认层级
|
||||
...config
|
||||
},
|
||||
prefab: null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量注册页面
|
||||
* @param views 页面配置映射
|
||||
*/
|
||||
registerAll(views: Record<string, ViewConfig>): void {
|
||||
for (const [viewId, config] of Object.entries(views)) {
|
||||
this.register(viewId, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面
|
||||
* @param viewId 页面唯一标识
|
||||
* @param options 打开选项
|
||||
*/
|
||||
open(viewId: string, options?: ViewOptions): void {
|
||||
if (!this._container) {
|
||||
const err = new Error('ViewManager: 未初始化,请先调用 init()');
|
||||
options?.onError?.(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const registered = this._registeredViews.get(viewId);
|
||||
if (!registered) {
|
||||
const err = new Error(`ViewManager: 页面 "${viewId}" 未注册`);
|
||||
options?.onError?.(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有缓存的实例
|
||||
const cachedView = this._viewCache.get(viewId);
|
||||
if (cachedView && cachedView.node.isValid) {
|
||||
this._showView(cachedView, options);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有缓存的预制体
|
||||
if (registered.prefab) {
|
||||
this._instantiateView(viewId, registered.prefab, options);
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态加载预制体
|
||||
resources.load(registered.config.prefabPath, Prefab, (err, prefab) => {
|
||||
if (err) {
|
||||
error(`ViewManager: 加载预制体失败 "${registered.config.prefabPath}"`, err);
|
||||
options?.onError?.(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 缓存预制体
|
||||
registered.prefab = prefab;
|
||||
this._instantiateView(viewId, prefab!, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化视图
|
||||
*/
|
||||
private _instantiateView(viewId: string, prefab: Prefab, options?: ViewOptions): void {
|
||||
if (!this._container) return;
|
||||
|
||||
const registered = this._registeredViews.get(viewId);
|
||||
if (!registered) return;
|
||||
|
||||
const node = instantiate(prefab);
|
||||
const view = node.getComponent(BaseView);
|
||||
|
||||
if (!view) {
|
||||
error(`ViewManager: 预制体 "${registered.config.prefabPath}" 缺少 BaseView 组件`);
|
||||
node.destroy();
|
||||
options?.onError?.(new Error('缺少 BaseView 组件'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置视图属性
|
||||
view.viewId = viewId;
|
||||
view.config = registered.config;
|
||||
view.setParams(options?.params);
|
||||
|
||||
// 设置层级
|
||||
node.setSiblingIndex(registered.config.zIndex || 0);
|
||||
|
||||
// 添加到容器
|
||||
this._container.addChild(node);
|
||||
|
||||
// 调用加载回调
|
||||
view.onViewLoad();
|
||||
|
||||
// 缓存视图实例
|
||||
if (registered.config.cache) {
|
||||
this._viewCache.set(viewId, view);
|
||||
}
|
||||
|
||||
// 显示视图
|
||||
this._showView(view, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示视图
|
||||
*/
|
||||
private _showView(view: BaseView, options?: ViewOptions): void {
|
||||
// 隐藏当前页面
|
||||
const currentView = this.getCurrentView();
|
||||
if (currentView && currentView !== view) {
|
||||
currentView._doHide();
|
||||
}
|
||||
|
||||
// 设置参数
|
||||
if (options?.params !== undefined) {
|
||||
view.setParams(options?.params);
|
||||
}
|
||||
|
||||
// 入栈
|
||||
if (!this._viewStack.includes(view)) {
|
||||
this._viewStack.push(view);
|
||||
}
|
||||
|
||||
// 显示
|
||||
view._doShow();
|
||||
|
||||
// 回调
|
||||
options?.onComplete?.(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭当前页面
|
||||
* @param options 关闭选项
|
||||
*/
|
||||
close(options?: { destroy?: boolean }): void {
|
||||
const currentView = this._viewStack.pop();
|
||||
if (!currentView) return;
|
||||
|
||||
const shouldDestroy = options?.destroy ?? !currentView.config?.cache;
|
||||
this._hideAndDestroyView(currentView, shouldDestroy);
|
||||
|
||||
// 显示上一页
|
||||
const prevView = this.getCurrentView();
|
||||
if (prevView) {
|
||||
prevView._doShow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页(close 的别名)
|
||||
*/
|
||||
back(): void {
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换当前页面
|
||||
* @param viewId 新页面标识
|
||||
* @param options 打开选项
|
||||
*/
|
||||
replace(viewId: string, options?: ViewOptions): void {
|
||||
const currentView = this.getCurrentView();
|
||||
if (currentView) {
|
||||
this._viewStack.pop();
|
||||
const shouldDestroy = !currentView.config?.cache;
|
||||
this._hideAndDestroyView(currentView, shouldDestroy);
|
||||
}
|
||||
|
||||
this.open(viewId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏并销毁视图(内部方法)
|
||||
*/
|
||||
private _hideAndDestroyView(view: BaseView, shouldDestroy: boolean): void {
|
||||
view._doHide();
|
||||
if (shouldDestroy) {
|
||||
this._viewCache.delete(view.viewId);
|
||||
view._doDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面
|
||||
*/
|
||||
getCurrentView(): BaseView | null {
|
||||
return this._viewStack.length > 0
|
||||
? this._viewStack[this._viewStack.length - 1]
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面栈
|
||||
*/
|
||||
getViewStack(): BaseView[] {
|
||||
return [...this._viewStack];
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有页面
|
||||
*/
|
||||
clearAll(): void {
|
||||
// 从栈顶开始销毁
|
||||
while (this._viewStack.length > 0) {
|
||||
const view = this._viewStack.pop()!;
|
||||
const shouldDestroy = !view.config?.cache;
|
||||
this._hideAndDestroyView(view, shouldDestroy);
|
||||
}
|
||||
|
||||
// 销毁缓存的页面
|
||||
for (const view of this._viewCache.values()) {
|
||||
if (view.node.isValid) {
|
||||
view._doDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
this._viewCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载页面预制体
|
||||
* @param viewId 页面标识
|
||||
* @param onProgress 进度回调
|
||||
* @param onComplete 完成回调
|
||||
*/
|
||||
preload(viewId: string, onProgress?: (progress: number) => void, onComplete?: () => void): void {
|
||||
const registered = this._registeredViews.get(viewId);
|
||||
if (!registered) {
|
||||
error(`ViewManager: 页面 "${viewId}" 未注册`);
|
||||
onComplete?.();
|
||||
return;
|
||||
}
|
||||
|
||||
// 已缓存
|
||||
if (registered.prefab) {
|
||||
onProgress?.(1);
|
||||
onComplete?.();
|
||||
return;
|
||||
}
|
||||
|
||||
resources.load(registered.config.prefabPath, Prefab, (err, prefab) => {
|
||||
if (err) {
|
||||
error(`ViewManager: 预加载失败 "${registered.config.prefabPath}"`, err);
|
||||
} else {
|
||||
registered.prefab = prefab;
|
||||
onProgress?.(1);
|
||||
}
|
||||
onComplete?.();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量预加载页面
|
||||
* @param viewIds 页面标识数组
|
||||
* @param onProgress 总进度回调 (0-1)
|
||||
* @param onComplete 完成回调
|
||||
*/
|
||||
preloadAll(viewIds: string[], onProgress?: (progress: number) => void, onComplete?: () => void): void {
|
||||
if (viewIds.length === 0) {
|
||||
onProgress?.(1);
|
||||
onComplete?.();
|
||||
return;
|
||||
}
|
||||
|
||||
let completed = 0;
|
||||
const total = viewIds.length;
|
||||
|
||||
for (const viewId of viewIds) {
|
||||
this.preload(viewId, () => {
|
||||
completed++;
|
||||
onProgress?.(completed / total);
|
||||
if (completed === total) {
|
||||
onComplete?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/scripts/core/ViewManager.ts.meta
Normal file
9
assets/scripts/core/ViewManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c2d3e4f5-a6b7-7890-cdef-123456789012",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user