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>
This commit is contained in:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -0,0 +1,146 @@
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}`);
}