import { _decorator, Component, SpriteFrame, sp } from 'cc'; import { BundleLoader } from './BundleLoader'; import { LevelConfigManager } from '../LevelSystem/LevelConfigManager'; import { SaveDataManager } from '../LevelSystem/SaveDataManager'; import { ConfigManager } from './ConfigManager'; import EventBus, { GameEvents } from './EventBus'; const { ccclass } = _decorator; /** * 资源预加载管理器 * 解决微信开发者平台动态加载资源不及时的问题 * 在进入战斗前预加载当前关卡的背景图片和敌人资源 */ @ccclass('ResourcePreloader') export class ResourcePreloader { private static instance: ResourcePreloader = null; private bundleLoader: BundleLoader; private levelConfigManager: LevelConfigManager; private saveDataManager: SaveDataManager; private configManager: ConfigManager; // 预加载缓存 private preloadedBackgrounds: Map = new Map(); private preloadedEnemyAnimations: Map = new Map(); // 预加载状态 private isPreloading: boolean = false; private preloadProgress: number = 0; private preloadErrors: string[] = []; public static getInstance(): ResourcePreloader { if (!ResourcePreloader.instance) { ResourcePreloader.instance = new ResourcePreloader(); } return ResourcePreloader.instance; } constructor() { this.bundleLoader = BundleLoader.getInstance(); this.levelConfigManager = LevelConfigManager.getInstance(); this.saveDataManager = SaveDataManager.getInstance(); this.configManager = ConfigManager.getInstance(); } /** * 预加载指定关卡的资源 * @param levelId 关卡ID */ public async preloadLevelResources(levelId: number): Promise { try { console.log(`[ResourcePreloader] 开始预加载关卡 ${levelId} 的资源`); EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_START); const levelConfig = await this.levelConfigManager.getLevelConfig(levelId); if (!levelConfig) { console.warn(`[ResourcePreloader] 找不到关卡 ${levelId} 的配置`); EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_ERROR, { error: `找不到关卡 ${levelId} 的配置` }); return false; } let totalResources = 0; let loadedResources = 0; // 计算总资源数 if (levelConfig.backgroundImage) { totalResources++; } const enemyTypes = new Set(); if (levelConfig.waves) { levelConfig.waves.forEach(wave => { if (wave.enemies && Array.isArray(wave.enemies)) { wave.enemies.forEach(enemy => { if (enemy.enemyType) { enemyTypes.add(enemy.enemyType); } }); } }); } totalResources += enemyTypes.size; console.log(`[ResourcePreloader] 关卡 ${levelId} 需要预加载 ${totalResources} 个资源`); // 预加载背景图片 if (levelConfig.backgroundImage) { try { EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_PROGRESS, { progress: (loadedResources / totalResources) * 100, message: `正在加载背景图片: ${levelConfig.backgroundImage}` }); await this.preloadBackground(levelConfig.backgroundImage); loadedResources++; EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_PROGRESS, { progress: (loadedResources / totalResources) * 100, message: `背景图片加载完成: ${levelConfig.backgroundImage}` }); console.log(`[ResourcePreloader] 背景图片预加载完成: ${levelConfig.backgroundImage}`); } catch (error) { console.warn(`[ResourcePreloader] 背景图片预加载失败: ${levelConfig.backgroundImage}`, error); } } // 预加载敌人动画 for (const enemyType of enemyTypes) { try { EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_PROGRESS, { progress: (loadedResources / totalResources) * 100, message: `正在加载敌人动画: ${enemyType}` }); await this.preloadEnemyAnimation(enemyType); loadedResources++; EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_PROGRESS, { progress: (loadedResources / totalResources) * 100, message: `敌人动画加载完成: ${enemyType}` }); console.log(`[ResourcePreloader] 敌人动画预加载完成: ${enemyType}`); } catch (error) { console.warn(`[ResourcePreloader] 敌人动画预加载失败: ${enemyType}`, error); } } console.log(`[ResourcePreloader] 关卡 ${levelId} 资源预加载完成,成功加载 ${loadedResources}/${totalResources} 个资源`); EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_COMPLETE); return true; } catch (error) { console.error(`[ResourcePreloader] 关卡 ${levelId} 资源预加载失败:`, error); EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_ERROR, { error: error.toString() }); return false; } } /** * 预加载当前关卡的所有资源 */ public async preloadCurrentLevelResources(): Promise { const currentLevel = this.saveDataManager.getCurrentLevel(); const success = await this.preloadLevelResources(currentLevel); if (!success) { throw new Error('资源预加载失败'); } } /** * 预加载背景图片 * @param backgroundPath 背景图片路径 */ private async preloadBackground(backgroundPath: string): Promise { // 如果已经预加载过,直接返回 if (this.preloadedBackgrounds.has(backgroundPath)) { return; } // 转换路径格式:从 "images/LevelBackground/BG1" 转换为 "LevelBackground/BG1" const bundlePath = backgroundPath.replace('images/', ''); const spriteFramePath = `${bundlePath}/spriteFrame`; try { const spriteFrame = await this.bundleLoader.loadSpriteFrame(spriteFramePath); this.preloadedBackgrounds.set(backgroundPath, spriteFrame); console.log(`[ResourcePreloader] 背景图片缓存成功: ${backgroundPath}`); } catch (error) { console.error(`[ResourcePreloader] 背景图片预加载失败: ${backgroundPath}`, error); throw error; } } /** * 预加载敌人动画资源 * @param enemyType 敌人类型 */ private async preloadEnemyAnimation(enemyType: string): Promise { // 如果已经预加载过,直接返回 if (this.preloadedEnemyAnimations.has(enemyType)) { return; } try { // 获取敌人配置 const enemiesConfig = this.configManager.getAllEnemies(); if (!enemiesConfig || enemiesConfig.length === 0) { throw new Error('敌人配置未加载'); } const enemyConfig = enemiesConfig.find((enemy: any) => enemy.id === enemyType); if (!enemyConfig) { throw new Error(`未找到敌人类型: ${enemyType}`); } // 获取敌人动画路径 let spinePath = enemyConfig.visualConfig?.spritePath || enemyConfig.visual?.sprite_path; if (!spinePath) { throw new Error(`敌人 ${enemyType} 没有配置动画路径`); } // 路径转换逻辑(与EnemyController保持一致) if (spinePath.startsWith('@EnemyAni')) { spinePath = spinePath.replace('@EnemyAni', 'Animation/EnemyAni'); } if (spinePath.startsWith('@')) { spinePath = spinePath.substring(1); } // 对于Animation/EnemyAni路径,需要添加子目录和文件名 if (spinePath.startsWith('Animation/EnemyAni/')) { const parts = spinePath.split('/'); if (parts.length === 3) { const animId = parts[2]; spinePath = `${spinePath}/${animId}`; } } // 移除Animation/前缀,因为BundleLoader会从Bundle中加载 if (spinePath.startsWith('Animation/')) { spinePath = spinePath.replace('Animation/', ''); } // 预加载骨骼动画数据 const skeletonData = await BundleLoader.loadSkeletonData(spinePath); this.preloadedEnemyAnimations.set(enemyType, skeletonData); console.log(`[ResourcePreloader] 敌人动画缓存成功: ${enemyType} -> ${spinePath}`); } catch (error) { console.error(`[ResourcePreloader] 敌人动画预加载失败: ${enemyType}`, error); throw error; } } /** * 计算需要预加载的资源总数 * @param levelConfig 关卡配置 * @returns 资源总数 */ private calculateTotalResources(levelConfig: any): number { let total = 0; // 背景图片 if (levelConfig.backgroundImage) { total += 1; } // 敌人动画 const enemyTypes = this.extractEnemyTypes(levelConfig); total += enemyTypes.length; return total; } /** * 从关卡配置中提取所有敌人类型 * @param levelConfig 关卡配置 * @returns 敌人类型数组 */ private extractEnemyTypes(levelConfig: any): string[] { const enemyTypes = new Set(); if (levelConfig.waves && Array.isArray(levelConfig.waves)) { for (const wave of levelConfig.waves) { if (wave.enemies && Array.isArray(wave.enemies)) { for (const enemy of wave.enemies) { if (enemy.enemyType) { enemyTypes.add(enemy.enemyType); } } } } } return Array.from(enemyTypes); } /** * 更新预加载进度 * @param loaded 已加载数量 * @param total 总数量 */ private updateProgress(loaded: number, total: number): void { this.preloadProgress = total > 0 ? (loaded / total) * 100 : 100; // 发送进度更新事件 EventBus.getInstance().emit(GameEvents.RESOURCE_PRELOAD_PROGRESS, { progress: this.preloadProgress, loaded: loaded, total: total }); } /** * 获取预加载的背景图片 * @param backgroundPath 背景图片路径 * @returns SpriteFrame或null */ public getPreloadedBackground(backgroundPath: string): SpriteFrame | null { return this.preloadedBackgrounds.get(backgroundPath) || null; } /** * 获取预加载的敌人动画 * @param enemyType 敌人类型 * @returns SkeletonData或null */ public getPreloadedEnemyAnimation(enemyType: string): sp.SkeletonData | null { return this.preloadedEnemyAnimations.get(enemyType) || null; } /** * 清理预加载缓存 */ public clearCache(): void { this.preloadedBackgrounds.clear(); this.preloadedEnemyAnimations.clear(); console.log('[ResourcePreloader] 预加载缓存已清理'); } /** * 获取预加载状态 */ public getPreloadStatus(): { isPreloading: boolean; progress: number; errors: string[]; } { return { isPreloading: this.isPreloading, progress: this.preloadProgress, errors: [...this.preloadErrors] }; } }