From b05ef713684315f6ce14c8ae36fb8ef74bce350a Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 16 Mar 2026 20:54:26 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=85=B3=E5=8D=A1?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=8A=A0=E8=BD=BD=E7=AD=96=E7=95=A5=E4=B8=BA?= =?UTF-8?q?=E6=8C=89=E9=9C=80=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 初始化时只预加载第一关图片,大幅减少启动时间 - 进入关卡后自动预加载下一关图片(静默加载) - 新增 ensureLevelReady 和 preloadNextLevel 方法支持按需加载 - 使用 Map 存储关卡配置,Set 跟踪加载中状态避免重复加载 - 提取 _createRuntimeConfig 方法减少代码重复 --- assets/prefabs/PageLevel.ts | 3 + assets/scripts/utils/LevelDataManager.ts | 188 ++++++++++++++++------- 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/assets/prefabs/PageLevel.ts b/assets/prefabs/PageLevel.ts index be1d5b8..4efb61e 100644 --- a/assets/prefabs/PageLevel.ts +++ b/assets/prefabs/PageLevel.ts @@ -172,6 +172,9 @@ export class PageLevel extends BaseView { // 更新倒计时显示 this.updateClockLabel(); + // 预加载下一关图片(静默加载,不阻塞) + LevelDataManager.instance.preloadNextLevel(this.currentLevelIndex); + console.log(`[PageLevel] 初始化关卡 ${this.currentLevelIndex + 1}, 答案长度: ${config.answer.length}`); } diff --git a/assets/scripts/utils/LevelDataManager.ts b/assets/scripts/utils/LevelDataManager.ts index 5ecf3aa..5e005f5 100644 --- a/assets/scripts/utils/LevelDataManager.ts +++ b/assets/scripts/utils/LevelDataManager.ts @@ -11,7 +11,7 @@ export type ProgressCallback = (progress: number, message: string) => void; /** * 关卡数据管理器 - * 单例模式,负责从 API 获取关卡数据并预加载图片 + * 单例模式,负责从 API 获取关卡数据并按需加载图片 */ export class LevelDataManager { private static _instance: LevelDataManager | null = null; @@ -22,8 +22,11 @@ export class LevelDataManager { /** 请求超时时间(毫秒) */ private readonly REQUEST_TIMEOUT = 8000; - /** 运行时关卡配置缓存 */ - private _levelConfigs: RuntimeLevelConfig[] = []; + /** API 返回的原始关卡数据 */ + private _apiData: ApiLevelData[] = []; + + /** 运行时关卡配置缓存(按需填充) */ + private _levelConfigs: Map = new Map(); /** 是否已成功从 API 获取数据 */ private _hasApiData: boolean = false; @@ -31,6 +34,9 @@ export class LevelDataManager { /** 图片缓存:URL -> SpriteFrame */ private _imageCache: Map = new Map(); + /** 正在加载中的关卡索引集合 */ + private _loadingLevels: Set = new Set(); + /** * 获取单例实例 */ @@ -47,7 +53,7 @@ export class LevelDataManager { private constructor() {} /** - * 初始化:从 API 获取数据并预加载图片 + * 初始化:从 API 获取数据并预加载第一关图片 * @param onProgress 进度回调 * @returns 是否初始化成功 */ @@ -55,34 +61,37 @@ export class LevelDataManager { console.log('[LevelDataManager] 开始初始化'); try { - // 阶段1: 获取 API 数据 (0-20%) + // 阶段1: 获取 API 数据 (0-30%) onProgress?.(0, '正在获取关卡数据...'); const apiData = await this._fetchApiData(); if (!apiData || apiData.length === 0) { console.warn('[LevelDataManager] API 返回空数据'); - onProgress?.(0.2, 'API 数据为空,使用本地配置'); + onProgress?.(0.3, 'API 数据为空,使用本地配置'); return false; } console.log(`[LevelDataManager] 获取到 ${apiData.length} 个关卡数据`); - onProgress?.(0.2, `获取到 ${apiData.length} 个关卡`); - - // 阶段2: 预加载所有图片 (20-80%) - const configs = await this._preloadImages(apiData, (progress) => { - onProgress?.(0.2 + progress * 0.6, '正在加载关卡资源...'); - }); - - this._levelConfigs = configs; + this._apiData = apiData; this._hasApiData = true; + onProgress?.(0.3, `获取到 ${apiData.length} 个关卡`); - console.log('[LevelDataManager] 初始化完成'); - onProgress?.(0.8, '关卡资源加载完成'); + // 阶段2: 只预加载第一关图片 (30-80%) + const firstLevel = apiData[0]; + onProgress?.(0.3, '正在加载第一关资源...'); + + const spriteFrame = await this._loadImage(firstLevel.imageUrl); + if (spriteFrame) { + this._levelConfigs.set(0, this._createRuntimeConfig(firstLevel, spriteFrame)); + } + + console.log('[LevelDataManager] 初始化完成,第一关资源已加载'); + onProgress?.(0.8, '第一关资源加载完成'); return true; } catch (error) { console.error('[LevelDataManager] 初始化失败:', error); - onProgress?.(0.2, '获取数据失败,使用本地配置'); + onProgress?.(0.3, '获取数据失败,使用本地配置'); return false; } } @@ -92,24 +101,107 @@ export class LevelDataManager { * @param index 关卡索引 */ getLevelConfig(index: number): RuntimeLevelConfig | null { - if (index < 0 || index >= this._levelConfigs.length) { - return null; - } - return this._levelConfigs[index]; + return this._levelConfigs.get(index) ?? null; } /** * 获取关卡总数 */ getLevelCount(): number { - return this._levelConfigs.length; + return this._apiData.length; } /** * 检查是否有 API 数据 */ hasApiData(): boolean { - return this._hasApiData && this._levelConfigs.length > 0; + return this._hasApiData && this._apiData.length > 0; + } + + /** + * 检查指定关卡图片是否已加载 + * @param index 关卡索引 + */ + isLevelImageLoaded(index: number): boolean { + return this._levelConfigs.has(index); + } + + /** + * 确保指定关卡资源已准备好 + * 如果资源未加载,会立即加载 + * @param index 关卡索引 + * @returns 加载的关卡配置,失败返回 null + */ + async ensureLevelReady(index: number): Promise { + // 检查索引有效性 + if (index < 0 || index >= this._apiData.length) { + console.warn(`[LevelDataManager] 关卡索引无效: ${index}`); + return null; + } + + // 检查缓存 + const cached = this._levelConfigs.get(index); + if (cached) { + return cached; + } + + // 检查是否正在加载 + if (this._loadingLevels.has(index)) { + console.log(`[LevelDataManager] 关卡 ${index} 正在加载中...`); + return null; + } + + // 开始加载 + this._loadingLevels.add(index); + console.log(`[LevelDataManager] 开始加载关卡 ${index} 资源...`); + + const data = this._apiData[index]; + const spriteFrame = await this._loadImage(data.imageUrl); + this._loadingLevels.delete(index); + + if (!spriteFrame) { + console.error(`[LevelDataManager] 加载关卡 ${index} 图片失败`); + return null; + } + + const config = this._createRuntimeConfig(data, spriteFrame); + this._levelConfigs.set(index, config); + console.log(`[LevelDataManager] 关卡 ${index} 资源加载完成`); + + return config; + } + + /** + * 预加载下一关图片(静默加载,不阻塞) + * 在进入当前关卡后调用,提前加载下一关资源 + * @param currentIndex 当前关卡索引 + */ + preloadNextLevel(currentIndex: number): void { + const nextIndex = currentIndex + 1; + + // 检查是否有下一关 + if (nextIndex >= this._apiData.length) { + console.log('[LevelDataManager] 没有下一关了'); + return; + } + + // 检查是否已加载 + if (this._levelConfigs.has(nextIndex)) { + console.log(`[LevelDataManager] 下一关 ${nextIndex} 已加载`); + return; + } + + // 检查是否正在加载 + if (this._loadingLevels.has(nextIndex)) { + console.log(`[LevelDataManager] 下一关 ${nextIndex} 正在加载中`); + return; + } + + // 异步加载,不等待 + console.log(`[LevelDataManager] 开始预加载下一关 ${nextIndex}...`); + this.ensureLevelReady(nextIndex).catch(err => { + console.error(`[LevelDataManager] 预加载下一关失败:`, err); + }); } /** @@ -132,35 +224,20 @@ export class LevelDataManager { } /** - * 预加载所有图片 - * @param apiData API 返回的关卡数据 - * @param onProgress 进度回调 + * 创建运行时关卡配置 + * @param data API 关卡数据 + * @param spriteFrame 已加载的精灵帧 */ - private async _preloadImages( - apiData: ApiLevelData[], - onProgress?: (progress: number) => void - ): Promise { - const configs: RuntimeLevelConfig[] = []; - const total = apiData.length; - - for (let i = 0; i < total; i++) { - const data = apiData[i]; - const spriteFrame = await this._loadImage(data.imageUrl); - - configs.push({ - id: data.id, - name: `第${data.level}关`, - spriteFrame: spriteFrame, - clue1: data.hint1, - clue2: data.hint2, - clue3: data.hint3, - answer: data.answer - }); - - onProgress?.((i + 1) / total); - } - - return configs; + private _createRuntimeConfig(data: ApiLevelData, spriteFrame: SpriteFrame | null): RuntimeLevelConfig { + return { + id: data.id, + name: `第${data.level}关`, + spriteFrame: spriteFrame, + clue1: data.hint1, + clue2: data.hint2, + clue3: data.hint3, + answer: data.answer + }; } /** @@ -169,8 +246,9 @@ export class LevelDataManager { */ private async _loadImage(url: string): Promise { // 检查缓存 - if (this._imageCache.has(url)) { - return this._imageCache.get(url)!; + const cached = this._imageCache.get(url); + if (cached) { + return cached; } return new Promise((resolve) => { @@ -199,7 +277,9 @@ export class LevelDataManager { * 清除缓存 */ clearCache(): void { - this._levelConfigs = []; + this._apiData = []; + this._levelConfigs.clear(); + this._loadingLevels.clear(); this._hasApiData = false; this._imageCache.clear(); console.log('[LevelDataManager] 缓存已清除');