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:
155
tools/split-level-bundles.js
Normal file
155
tools/split-level-bundles.js
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 将 build/assets/level-prefabs 拆为:
|
||||
* - shell: config.json + index.js(进 assets_all 首屏)
|
||||
* - 每关一包: assets/level-prefabs/import/.../*.json
|
||||
*
|
||||
* 输出 levels-manifest.json 供 loader 按 levelId 按需下载。
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { formatBytes } = require('./package-optimize');
|
||||
|
||||
function mkdirp(p) { fs.mkdirSync(p, { recursive: true }); }
|
||||
|
||||
function hashFileMd5(filePath) {
|
||||
return crypto.createHash('md5').update(fs.readFileSync(filePath)).digest('hex');
|
||||
}
|
||||
|
||||
function zipDir(srcDir, outFile) {
|
||||
const absOut = path.resolve(outFile);
|
||||
mkdirp(path.dirname(absOut));
|
||||
if (fs.existsSync(absOut)) fs.unlinkSync(absOut);
|
||||
execSync(`cd "${path.resolve(srcDir)}" && zip -0 -q -r "${absOut}" .`, { stdio: 'pipe' });
|
||||
}
|
||||
|
||||
function walkJsonFiles(dir, out = []) {
|
||||
if (!fs.existsSync(dir)) return out;
|
||||
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, ent.name);
|
||||
if (ent.isDirectory()) walkJsonFiles(p, out);
|
||||
else if (ent.name.endsWith('.json')) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 扫描 import/*.json,从预制体名 Level{id} 建立 levelId → 相对路径 */
|
||||
function indexImportFiles(levelPrefabsDir) {
|
||||
const importRoot = path.join(levelPrefabsDir, 'import');
|
||||
const byLevelId = new Map();
|
||||
for (const abs of walkJsonFiles(importRoot)) {
|
||||
const rel = path.relative(levelPrefabsDir, abs).split(path.sep).join('/');
|
||||
const txt = fs.readFileSync(abs, 'utf8');
|
||||
const m = /"Level(\d+)"/.exec(txt);
|
||||
if (!m) continue;
|
||||
const levelId = m[1];
|
||||
if (byLevelId.has(levelId)) {
|
||||
console.warn(`>>> 警告: Level${levelId} 重复 import,保留 ${byLevelId.get(levelId)}`);
|
||||
continue;
|
||||
}
|
||||
byLevelId.set(levelId, rel);
|
||||
}
|
||||
return byLevelId;
|
||||
}
|
||||
|
||||
/** 从 config.json 校验 path ↔ uuid */
|
||||
function readConfigLevels(levelPrefabsDir) {
|
||||
const cfg = JSON.parse(fs.readFileSync(path.join(levelPrefabsDir, 'config.json'), 'utf8'));
|
||||
const out = new Map();
|
||||
for (const [idx, entry] of Object.entries(cfg.paths || {})) {
|
||||
const m = /^level-prefabs\/Level(\d+)$/.exec(entry[0]);
|
||||
if (!m) continue;
|
||||
out.set(m[1], {
|
||||
path: entry[0],
|
||||
pathIndex: idx,
|
||||
uuid: (cfg.uuids || [])[+idx],
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} levelPrefabsDir Cocos 构建产物 assets/level-prefabs
|
||||
* @param {string} webglDir 输出 WebGL/*.bundle
|
||||
* @param {string} manifestOutPath 输出 levels-manifest.json
|
||||
*/
|
||||
function splitLevelBundles(levelPrefabsDir, webglDir, manifestOutPath, packTmpDir) {
|
||||
if (!fs.existsSync(path.join(levelPrefabsDir, 'config.json'))) {
|
||||
throw new Error(`缺少 level-prefabs/config.json: ${levelPrefabsDir}`);
|
||||
}
|
||||
|
||||
const configLevels = readConfigLevels(levelPrefabsDir);
|
||||
const importByLevel = indexImportFiles(levelPrefabsDir);
|
||||
mkdirp(webglDir);
|
||||
const stageBase = packTmpDir || path.join(webglDir, '..', '.level-pack-tmp');
|
||||
mkdirp(stageBase);
|
||||
|
||||
const manifest = {
|
||||
version: 1,
|
||||
shell: ['assets/level-prefabs/config.json', 'assets/level-prefabs/index.js'],
|
||||
levels: {},
|
||||
};
|
||||
|
||||
let packed = 0;
|
||||
let totalBytes = 0;
|
||||
const missing = [];
|
||||
|
||||
for (const [levelId, meta] of configLevels) {
|
||||
const importRel = importByLevel.get(levelId);
|
||||
if (!importRel) {
|
||||
missing.push(levelId);
|
||||
continue;
|
||||
}
|
||||
const stageRoot = path.join(stageBase, levelId);
|
||||
const assetRoot = path.join(stageRoot, 'assets', 'level-prefabs');
|
||||
mkdirp(path.dirname(path.join(assetRoot, importRel)));
|
||||
fs.copyFileSync(
|
||||
path.join(levelPrefabsDir, importRel),
|
||||
path.join(assetRoot, importRel),
|
||||
);
|
||||
|
||||
const zipTmp = path.join(stageBase, `${levelId}.zip`);
|
||||
zipDir(stageRoot, zipTmp);
|
||||
const hash = hashFileMd5(zipTmp);
|
||||
const bundleName = `defaultlocalgroup_level_${levelId}_${hash}.bundle`;
|
||||
fs.copyFileSync(zipTmp, path.join(webglDir, bundleName));
|
||||
const size = fs.statSync(zipTmp).size;
|
||||
totalBytes += size;
|
||||
|
||||
manifest.levels[levelId] = {
|
||||
bundle: bundleName,
|
||||
path: meta.path,
|
||||
uuid: meta.uuid,
|
||||
files: [`assets/level-prefabs/${importRel}`],
|
||||
bytes: size,
|
||||
};
|
||||
packed += 1;
|
||||
fs.rmSync(stageRoot, { recursive: true, force: true });
|
||||
fs.unlinkSync(zipTmp);
|
||||
}
|
||||
|
||||
fs.rmSync(stageBase, { recursive: true, force: true });
|
||||
|
||||
fs.writeFileSync(manifestOutPath, JSON.stringify(manifest), 'utf8');
|
||||
|
||||
console.log(`>>> 关卡分包: ${packed} 关, 合计 ${formatBytes(totalBytes)}, 均 ${formatBytes(Math.round(totalBytes / Math.max(packed, 1)))}/关`);
|
||||
if (missing.length) {
|
||||
console.warn(`>>> 警告: ${missing.length} 关在 config 中无 import 文件 (例: ${missing.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
return { manifest, packed, missing, totalBytes };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
splitLevelBundles,
|
||||
indexImportFiles,
|
||||
readConfigLevels,
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
const levelDir = path.resolve(process.argv[2]);
|
||||
const webgl = path.resolve(process.argv[3]);
|
||||
const manifest = path.resolve(process.argv[4] || path.join(path.dirname(webgl), 'levels-manifest.json'));
|
||||
splitLevelBundles(levelDir, webgl, manifest);
|
||||
}
|
||||
Reference in New Issue
Block a user