import { assetManager, Prefab, resources } from 'cc'; import { PREVIEW } from 'cc/env'; import { ensureResourcesBundle } from '../core/ResourcesBundle'; import { getLevelPrefabUuid, loadLevelPrefabUuidIndex, parseLevelIdFromPrefabPath, shouldTryEditorUuidLoad, } from './LevelPrefabUuidIndex'; const LEVEL_PREFAB_BUNDLE = 'level-prefabs'; let cachedLevelBundle: assetManager.Bundle | null = null; let levelBundlePromise: Promise | null = null; function loadLevelPrefabBundle(): Promise { if (cachedLevelBundle) return Promise.resolve(cachedLevelBundle); if (levelBundlePromise) return levelBundlePromise; levelBundlePromise = new Promise((resolve, reject) => { assetManager.loadBundle(LEVEL_PREFAB_BUNDLE, (err, bundle) => { levelBundlePromise = null; if (err || !bundle) { reject(err ?? new Error(`bundle "${LEVEL_PREFAB_BUNDLE}" unavailable`)); return; } cachedLevelBundle = bundle; resolve(bundle); }); }); return levelBundlePromise; } function loadPrefabFromBundle(bundle: assetManager.Bundle, path: string): Promise { return new Promise((resolve, reject) => { bundle.load(path, Prefab, (err, prefab) => { if (!err && prefab) resolve(prefab); else reject(err ?? new Error(`missing prefab: ${path}`)); }); }); } function loadPrefabFromResources(path: string): Promise { return new Promise((resolve, reject) => { resources.load(path, Prefab, (err, prefab) => { if (!err && prefab) resolve(prefab); else reject(err ?? new Error(`missing prefab: ${path}`)); }); }); } function loadPrefabByUuid(uuid: string): Promise { return new Promise((resolve, reject) => { assetManager.loadAny({ uuid }, (err: Error | null, asset: Prefab) => { if (err || !asset) { reject(err ?? new Error(`missing prefab uuid: ${uuid}`)); return; } resolve(asset); }); }); } /** bundle / resources 内可能的路径(子目录 level-prefabs/ 或 bundle 根下 LevelN) */ function prefabPathCandidates(path: string): string[] { const trimmed = path.trim(); const base = trimmed.replace(/^level-prefabs\//, ''); // bundle config 路径键为 level-prefabs/LevelN,裸 LevelN 会触发 loadAny 解析错误 return [...new Set([trimmed, `level-prefabs/${base}`])]; } async function loadFirstAvailable( loader: (p: string) => Promise, paths: string[], ): Promise { let lastErr: unknown; for (const p of paths) { try { return await loader(p); } catch (e) { lastErr = e; } } throw lastErr ?? new Error(`missing prefab: ${paths[0]}`); } async function tryLoadFromEditorUuid(path: string): Promise { if (!shouldTryEditorUuidLoad()) return null; await loadLevelPrefabUuidIndex(); const levelId = parseLevelIdFromPrefabPath(path); if (levelId === undefined) return null; const uuid = getLevelPrefabUuid(levelId); if (!uuid) return null; try { const prefab = await loadPrefabByUuid(uuid); console.log(`[LevelPrefabLoader] 编辑器 uuid 加载 Level${levelId}`); return prefab; } catch (e) { console.warn(`[LevelPrefabLoader] uuid 加载 Level${levelId} 失败`, e); return null; } } /** 进关前由 loader 注入:按 levelId 下载对应关卡包 */ async function ensureLevelPackForPath(path: string): Promise { const levelId = parseLevelIdFromPrefabPath(path); if (levelId === undefined) return; const hook = (globalThis as { __tfrhEnsureLevelPack?: (id: number) => Promise }).__tfrhEnsureLevelPack; if (typeof hook === 'function') await hook(levelId); cachedLevelBundle = null; levelBundlePromise = null; } /** 优先从 level-prefabs 分包加载;编辑器预览可回退 uuid / resources */ export async function loadLevelPrefab(path: string): Promise { await ensureResourcesBundle(); await ensureLevelPackForPath(path); const candidates = prefabPathCandidates(path); if (PREVIEW) { const byUuid = await tryLoadFromEditorUuid(path); if (byUuid?.isValid) return byUuid; } let bundleErr: unknown = null; try { const bundle = await loadLevelPrefabBundle(); return await loadFirstAvailable((p) => loadPrefabFromBundle(bundle, p), candidates); } catch (err) { bundleErr = err; console.warn('[LevelPrefabLoader] level-prefabs bundle 加载失败', err); } if (!PREVIEW) { const byUuid = await tryLoadFromEditorUuid(path); if (byUuid?.isValid) return byUuid; } console.error( '[LevelPrefabLoader] 关卡预制体未找到。请确认 bundle-level-prefabs 已标记为 Asset Bundle ' + `"${LEVEL_PREFAB_BUNDLE}",并运行: python3 tools/bake_cocos_level_prefabs.py`, bundleErr, ); throw bundleErr instanceof Error ? bundleErr : new Error(`missing prefab: ${path}`); }