feat: 添加页面管理系统和首页/关卡页面

- 实现 ViewManager 单例页面管理器,支持页面注册、打开、关闭、缓存
- 实现 BaseView 页面基类,提供统一的页面生命周期
- 添加 PageHome 首页,包含开始游戏按钮跳转功能
- 添加 PageLevel 关卡页面,继承 BaseView
- 更新 PageLoading 支持进度条显示和页面预加载
- 添加相关图片资源和预制体

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
richarjiang
2026-03-11 10:02:29 +08:00
parent 02a67909d6
commit 8986d8d8f2
29 changed files with 5456 additions and 61 deletions

View 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?.();
}
});
}
}
}