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

95
tools/runtime-pack.js Normal file
View File

@@ -0,0 +1,95 @@
/**
* 统一运行时包定义 — 本地 static/unity 与 OSS unitycdndir 内容完全一致。
*
* 运行时包(唯一真相):
* Build/
* StreamingAssets/
* levels-database.json
* levels-database.json.br
*
* 不含 index.html / TemplateData仅 standalone-player 独立调试页使用)
*/
const fs = require('fs');
const path = require('path');
const RUNTIME_ROOT_FILES = ['levels-database.json', 'levels-database.json.br'];
const RUNTIME_DIRS = ['Build', 'StreamingAssets'];
function walkFiles(root, bucket, prefix = '') {
if (!fs.existsSync(root)) return;
for (const ent of fs.readdirSync(root, { withFileTypes: true })) {
if (ent.name === '.DS_Store') continue;
const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
const full = path.join(root, ent.name);
if (ent.isDirectory()) walkFiles(full, bucket, rel);
else bucket.push({ rel, full, size: fs.statSync(full).size });
}
}
/** 列出运行时包内所有相对路径(用于 manifest / 校验) */
function listRuntimeFiles(packDir) {
const files = [];
for (const dir of RUNTIME_DIRS) {
walkFiles(path.join(packDir, dir), files, dir);
}
for (const name of RUNTIME_ROOT_FILES) {
const full = path.join(packDir, name);
if (fs.existsSync(full)) {
files.push({ rel: name, full, size: fs.statSync(full).size });
}
}
return files.sort((a, b) => a.rel.localeCompare(b.rel));
}
function bundleNamesFromCatalog(catalogPath) {
if (!fs.existsSync(catalogPath)) return [];
const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
return [...new Set(
(catalog.m_InternalIds || [])
.filter((id) => String(id).includes('.bundle'))
.map((id) => String(id).split('/').pop()),
)].sort();
}
function assertRuntimePack(packDir, opts) {
opts = opts || {};
const loader = path.join(packDir, 'Build/mstest5.loader.js');
const catalog = path.join(packDir, 'StreamingAssets/aa/catalog.json');
const levelsManifest = path.join(packDir, 'StreamingAssets/aa/levels-manifest.json');
if (!fs.existsSync(loader)) {
throw new Error(`缺少运行时包: ${loader}`);
}
if (!fs.existsSync(catalog)) {
throw new Error(`缺少运行时包: ${catalog}`);
}
if (!fs.existsSync(path.join(packDir, 'levels-database.json'))) {
throw new Error(`缺少运行时包: ${path.join(packDir, 'levels-database.json')}`);
}
const names = bundleNamesFromCatalog(catalog);
if (opts.requireLevelsBundle && !names.some((n) => n.includes('levels_all'))) {
throw new Error('catalog 缺少 levels_all 分包');
}
if (opts.requireLevelsManifest && !fs.existsSync(levelsManifest)) {
throw new Error(`缺少运行时包: ${levelsManifest}`);
}
if (opts.requireLevelsManifest) {
const manifest = JSON.parse(fs.readFileSync(levelsManifest, 'utf8'));
const count = Object.keys(manifest.levels || {}).length;
if (count < 1) throw new Error('levels-manifest.json 无关卡条目');
}
for (const name of names) {
const p = path.join(packDir, 'StreamingAssets/aa/WebGL', name);
if (!fs.existsSync(p) || fs.statSync(p).size < 100) {
throw new Error(`bundle 无效: ${p}`);
}
}
}
module.exports = {
RUNTIME_ROOT_FILES,
RUNTIME_DIRS,
walkFiles,
listRuntimeFiles,
bundleNamesFromCatalog,
assertRuntimePack,
};