Files
mp-xieyingeng/assets/scripts/utils/LevelDataManager.ts
2026-04-26 17:04:47 +08:00

273 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
import { RuntimeLevelConfig } from '../types/LevelTypes';
import { NextLevelData } from '../types/ApiTypes';
/**
* 进度回调类型
* @param progress 进度值 0-1
* @param message 进度消息
*/
export type ProgressCallback = (progress: number, message: string) => void;
/**
* 关卡数据管理器
* 单例模式,管理当前关卡和预加载关卡的图片资源
* 不再依赖全量关卡列表,由外部传入 NextLevelData 驱动
*/
export class LevelDataManager {
private static _instance: LevelDataManager | null = null;
/** 运行时关卡配置缓存(按 levelId 索引) */
private _levelConfigs: Map<string, RuntimeLevelConfig> = new Map();
/** 图片缓存URL -> SpriteFrame */
private _imageCache: Map<string, SpriteFrame> = new Map();
/** 正在加载中的关卡 ID 集合 */
private _loadingLevels: Set<string> = new Set();
/** 是否已初始化 */
private _initialized: boolean = false;
/**
* 获取单例实例
*/
static get instance(): LevelDataManager {
if (!this._instance) {
this._instance = new LevelDataManager();
}
return this._instance;
}
private constructor() {}
/**
* 初始化:加载首关图片
* 由 PageLoading 在获取 game-data 后调用,传入 nextLevel 数据
* @param nextLevel 首关数据(来自 game-data 接口)
* @param onProgress 进度回调
* @returns 是否初始化成功
*/
async initialize(nextLevel: NextLevelData, onProgress?: ProgressCallback): Promise<boolean> {
console.log(`[LevelDataManager] 开始初始化,加载关卡 ${nextLevel.level}`);
try {
onProgress?.(0.3, '正在加载游戏必备资源...');
const config = await this._loadLevelFromData(nextLevel);
if (!config) {
console.error('[LevelDataManager] 初始化失败:图片加载失败');
onProgress?.(0.3, '资源加载失败,请重新打开游戏');
return false;
}
this._initialized = true;
console.log('[LevelDataManager] 初始化完成');
onProgress?.(0.8, '游戏资源加载完成');
return true;
} catch (error) {
console.error('[LevelDataManager] 初始化失败:', error);
onProgress?.(0.3, '网络异常,请重新打开游戏');
return false;
}
}
/**
* 是否已初始化
*/
isInitialized(): boolean {
return this._initialized;
}
/**
* 获取指定关卡配置(按 ID
* @param levelId 关卡 ID
*/
getLevelConfig(levelId: string): RuntimeLevelConfig | null {
return this._levelConfigs.get(levelId) ?? null;
}
/**
* 用 enter 接口返回的数据更新运行时关卡配置(填充答案和线索)
* @param levelId 关卡 ID
* @param details enter 接口返回的详情
*/
updateLevelDetails(levelId: string, details: {
answer: string;
image1Description: string | null;
image2Description: string | null;
punchline: string | null;
hint1: string | null;
hint2: string | null;
hint3: string | null;
}): void {
const config = this._levelConfigs.get(levelId);
if (!config) {
console.warn(`[LevelDataManager] 关卡 ${levelId} 配置不存在,无法更新详情`);
return;
}
this._levelConfigs.set(levelId, {
...config,
answer: details.answer,
image1Description: details.image1Description ?? config.image1Description,
image2Description: details.image2Description ?? config.image2Description,
punchline: details.punchline ?? config.punchline,
clue1: details.hint1 ?? null,
clue2: details.hint2 ?? null,
clue3: details.hint3 ?? null,
});
console.log(`[LevelDataManager] 关卡 ${levelId} 详情已更新`);
}
/**
* 加载并缓存一个关卡(同步等待图片加载完成)
* 用于 game-data 返回的 nextLevel 或 complete 返回的 nextLevel
* @param data NextLevelData
* @returns 加载好的 RuntimeLevelConfig失败返回 null
*/
async ensureLevelReady(data: NextLevelData): Promise<RuntimeLevelConfig | null> {
// 检查缓存
const cached = this._levelConfigs.get(data.id);
if (cached) {
return cached;
}
// 检查是否正在加载
if (this._loadingLevels.has(data.id)) {
console.log(`[LevelDataManager] 关卡 ${data.id} 正在加载中...`);
return null;
}
return this._loadLevelFromData(data);
}
/**
* 预加载关卡图片(静默加载,不阻塞)
* 用于 enter 返回的 preloadNextLevel
* @param data NextLevelData
*/
preloadLevel(data: NextLevelData): void {
// 已缓存
if (this._levelConfigs.has(data.id)) {
console.log(`[LevelDataManager] 关卡 ${data.id} 已加载`);
return;
}
// 正在加载
if (this._loadingLevels.has(data.id)) {
console.log(`[LevelDataManager] 关卡 ${data.id} 正在加载中`);
return;
}
// 异步加载,不等待
console.log(`[LevelDataManager] 开始预加载关卡 ${data.id}...`);
this._loadLevelFromData(data).catch(err => {
console.error(`[LevelDataManager] 预加载关卡失败:`, err);
});
}
/**
* 检查指定关卡图片是否已加载
* @param levelId 关卡 ID
*/
isLevelImageLoaded(levelId: string): boolean {
return this._levelConfigs.has(levelId);
}
/**
* 清除缓存
*/
clearCache(): void {
this._levelConfigs.clear();
this._loadingLevels.clear();
this._imageCache.clear();
this._initialized = false;
console.log('[LevelDataManager] 缓存已清除');
}
/**
* 从 NextLevelData 加载图片并创建 RuntimeLevelConfig
*/
private async _loadLevelFromData(data: NextLevelData): Promise<RuntimeLevelConfig | null> {
this._loadingLevels.add(data.id);
console.log(`[LevelDataManager] 开始加载关卡 ${data.id} 资源...`);
try {
const [spriteFrame1, spriteFrame2] = await Promise.all([
this._loadImage(data.image1Url),
this._loadImage(data.image2Url),
]);
if (!spriteFrame1) {
console.error(`[LevelDataManager] 加载关卡 ${data.id} 图片1失败`);
return null;
}
const config = this._createRuntimeConfig(data, spriteFrame1, spriteFrame2);
this._levelConfigs.set(data.id, config);
console.log(`[LevelDataManager] 关卡 ${data.id} 资源加载完成`);
return config;
} finally {
this._loadingLevels.delete(data.id);
}
}
/**
* 创建运行时关卡配置
*/
private _createRuntimeConfig(data: NextLevelData, spriteFrame1: SpriteFrame | null, spriteFrame2: SpriteFrame | null): RuntimeLevelConfig {
return {
id: data.id,
name: `${data.level}`,
spriteFrame1,
spriteFrame2,
image1Description: data.image1Description,
image2Description: data.image2Description,
punchline: data.punchline,
clue1: data.hint1,
clue2: data.hint2,
clue3: data.hint3,
answer: data.answer,
completed: false,
timeLimit: data.timeLimit,
};
}
/**
* 加载远程图片为 SpriteFrame
* @param url 图片 URL
*/
private async _loadImage(url: string): Promise<SpriteFrame | null> {
// 检查缓存
const cached = this._imageCache.get(url);
if (cached) {
return cached;
}
return new Promise((resolve) => {
assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
if (err) {
console.error(`[LevelDataManager] 加载图片失败: ${url}`, err);
resolve(null);
return;
}
const texture = new Texture2D();
texture.image = imageAsset;
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
// 缓存
this._imageCache.set(url, spriteFrame);
resolve(spriteFrame);
});
});
}
}