Files
mp-xieyingeng/assets/scripts/utils/LevelDataManager.ts
richarjiang c54a404c12 feat: 接入关卡配置 API 并支持降级到本地配置
- 新增 LevelDataManager 单例管理关卡数据
- 新增 HttpUtil 封装 XMLHttpRequest 请求
- 新增 LevelTypes 类型定义
- PageLoading 集成 API 数据预加载(0-80% 进度)
- PageLevel 支持优先使用 API 数据,失败时降级到本地配置
- 字段映射: hint1/2/3 → clue1/2/3, imageUrl → SpriteFrame
2026-03-15 16:07:00 +08:00

208 lines
6.0 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 { HttpUtil } from './HttpUtil';
import { ApiLevelData, ApiResponse, RuntimeLevelConfig } from '../types/LevelTypes';
/**
* 进度回调类型
* @param progress 进度值 0-1
* @param message 进度消息
*/
export type ProgressCallback = (progress: number, message: string) => void;
/**
* 关卡数据管理器
* 单例模式,负责从 API 获取关卡数据并预加载图片
*/
export class LevelDataManager {
private static _instance: LevelDataManager | null = null;
/** API 地址 */
private readonly API_URL = 'https://ilookai.cn/api/v1/wechat-game/levels';
/** 请求超时时间(毫秒) */
private readonly REQUEST_TIMEOUT = 8000;
/** 运行时关卡配置缓存 */
private _levelConfigs: RuntimeLevelConfig[] = [];
/** 是否已成功从 API 获取数据 */
private _hasApiData: boolean = false;
/** 图片缓存URL -> SpriteFrame */
private _imageCache: Map<string, SpriteFrame> = new Map();
/**
* 获取单例实例
*/
static get instance(): LevelDataManager {
if (!this._instance) {
this._instance = new LevelDataManager();
}
return this._instance;
}
/**
* 私有构造函数
*/
private constructor() {}
/**
* 初始化:从 API 获取数据并预加载图片
* @param onProgress 进度回调
* @returns 是否初始化成功
*/
async initialize(onProgress?: ProgressCallback): Promise<boolean> {
console.log('[LevelDataManager] 开始初始化');
try {
// 阶段1: 获取 API 数据 (0-20%)
onProgress?.(0, '正在获取关卡数据...');
const apiData = await this._fetchApiData();
if (!apiData || apiData.length === 0) {
console.warn('[LevelDataManager] API 返回空数据');
onProgress?.(0.2, '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._hasApiData = true;
console.log('[LevelDataManager] 初始化完成');
onProgress?.(0.8, '关卡资源加载完成');
return true;
} catch (error) {
console.error('[LevelDataManager] 初始化失败:', error);
onProgress?.(0.2, '获取数据失败,使用本地配置');
return false;
}
}
/**
* 获取指定关卡配置
* @param index 关卡索引
*/
getLevelConfig(index: number): RuntimeLevelConfig | null {
if (index < 0 || index >= this._levelConfigs.length) {
return null;
}
return this._levelConfigs[index];
}
/**
* 获取关卡总数
*/
getLevelCount(): number {
return this._levelConfigs.length;
}
/**
* 检查是否有 API 数据
*/
hasApiData(): boolean {
return this._hasApiData && this._levelConfigs.length > 0;
}
/**
* 从 API 获取关卡数据
*/
private async _fetchApiData(): Promise<ApiLevelData[] | null> {
try {
const response = await HttpUtil.get<ApiResponse>(this.API_URL, this.REQUEST_TIMEOUT);
if (response.code !== 0) {
console.warn(`[LevelDataManager] API 返回错误码: ${response.code}, 消息: ${response.message}`);
return null;
}
return response.data;
} catch (error) {
console.error('[LevelDataManager] API 请求失败:', error);
return null;
}
}
/**
* 预加载所有图片
* @param apiData API 返回的关卡数据
* @param onProgress 进度回调
*/
private async _preloadImages(
apiData: ApiLevelData[],
onProgress?: (progress: number) => void
): Promise<RuntimeLevelConfig[]> {
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.name,
spriteFrame: spriteFrame,
clue1: data.hint1,
clue2: data.hint2,
clue3: data.hint3,
answer: data.answer
});
onProgress?.((i + 1) / total);
}
return configs;
}
/**
* 加载远程图片为 SpriteFrame
* @param url 图片 URL
*/
private async _loadImage(url: string): Promise<SpriteFrame | null> {
// 检查缓存
if (this._imageCache.has(url)) {
return this._imageCache.get(url)!;
}
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);
});
});
}
/**
* 清除缓存
*/
clearCache(): void {
this._levelConfigs = [];
this._hasApiData = false;
this._imageCache.clear();
console.log('[LevelDataManager] 缓存已清除');
}
}