Files
cocos/assets/scripts/level/LevelPrefabLoader.ts
刘宇飞 d393302388 Complete Cocos Creator port with level bundles, themes, and tooling.
Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 15:30:58 +08:00

147 lines
5.1 KiB
TypeScript
Raw Permalink 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 { 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<assetManager.Bundle> | null = null;
function loadLevelPrefabBundle(): Promise<assetManager.Bundle> {
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<Prefab> {
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<Prefab> {
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<Prefab> {
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<Prefab>,
paths: string[],
): Promise<Prefab> {
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<Prefab | null> {
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<void> {
const levelId = parseLevelIdFromPrefabPath(path);
if (levelId === undefined) return;
const hook = (globalThis as { __tfrhEnsureLevelPack?: (id: number) => Promise<void> }).__tfrhEnsureLevelPack;
if (typeof hook === 'function') await hook(levelId);
cachedLevelBundle = null;
levelBundlePromise = null;
}
/** 优先从 level-prefabs 分包加载;编辑器预览可回退 uuid / resources */
export async function loadLevelPrefab(path: string): Promise<Prefab> {
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}`);
}