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,474 @@
/**
* 用于导入 2d 项目到 3d 项目
*/
'use strict';
// @ts-ignore
import { v4 } from 'node-uuid';
import { nameToId } from './utlis';
import { relative, join, dirname, parse, extname, ParsedPath } from 'path';
// @ts-ignore
import { existsSync, ensureDirSync, copyFileSync, readJSONSync, writeJSONSync, readFileSync, writeFileSync } from 'fs-extra';
import {
migratePlatformSettings,
saveUuid,
getDefaultAssets2D,
getNewUuid,
import2DChunks,
importSubAssets, importProjectAssets,
} from './utlis';
import { UUID_2D_TO_3D, UUID_SKIP_EFFECT, UUID_UI_2D_TO_3D } from "./diff";
import { getConverter } from "../convertor";
export interface MessageInfo {
type: string; // 类型表示需要处理什么资源
json: any; // 实际资源的源数据
}
export abstract class ImporterBase {
public type: string = '';
// 导入到 3d 工程所在磁盘的路径
protected destFsPath: string = '';
protected destMetaFsPath: string = '';
// 2d 源文件所在磁盘的路径
protected sourceFsPath: string = '';
protected pathInfo: ParsedPath | null = null;
protected _2dMeta: any = null;
protected _3dMeta: any = null;
// 2d 源文件转成 3d 源文件,如果不为 null 说明需要保存
// 例如 animation、prefab、scene 之类的源文件
protected _2dTo3dSource: any = null;
// 检查 uuid 是否冲突,如果有就存储起来,后续会用到
async checkUuid(meta: any) {
const assetFsPath = await Editor.Message.request('asset-db', 'query-path', meta.uuid);
if (assetFsPath && assetFsPath !== this.destFsPath) {
const newUuid = v4();
saveUuid(meta.uuid, newUuid);
return newUuid;
}
// 存放 sprite frame uuid 对应的 texture uuid
if (meta.type === 'sprite' && meta.subMetas) {
for (const key in meta.subMetas) {
const subMeta = meta.subMetas[key];
saveUuid(subMeta.uuid, subMeta.rawTextureUuid);
}
}
return meta.uuid;
}
get3DUuid() {
try {
const meta = readJSONSync(this.destMetaFsPath);
return meta.uuid;
}
catch (e) {
return v4();
}
}
public reset() {
this.destFsPath = '';
this.sourceFsPath = '';
this.pathInfo = null;
this._2dMeta = null;
this._3dMeta = null;
this._2dTo3dSource = null;
}
public static getPathInfo(projectRoot: string, sourceFsPath: string) {
let relativePath = relative(projectRoot, sourceFsPath);
if (!relativePath.startsWith('assets')) {
relativePath = join('assets', relativePath);
}
let to = join(Editor.Project.path, relativePath);
// 改后缀名 .fire to .scene;
if (to.endsWith('.fire')) {
to = to.replace(/.fire+$/g, '.scene');
} else if (to.endsWith('.js')) {
const meta = readJSONSync(sourceFsPath + '.meta');
if (!meta.isPlugin) {
to = to.replace(/.js+$/g, '.ts');
}
}
return {
to: to,
toMeta: to + '.meta',
from: sourceFsPath,
fromMeta: sourceFsPath + '.meta',
pathInfo: parse(sourceFsPath),
};
}
public static isNew(projectRoot: string, sourceFsPath: string) {
try {
if (sourceFsPath.endsWith('assets')) {
return false;
}
const info = ImporterBase.getPathInfo(projectRoot, sourceFsPath);
if (existsSync(info.to) && existsSync(info.toMeta)) {
const _3DMeta = readJSONSync(info.toMeta);
if (_3DMeta.importer === 'directory') {
return false;
}
const _2DMeta = readJSONSync(info.fromMeta);
return _2DMeta.uuid !== _3DMeta.uuid;
}
return true;
}
catch (e) {
return true;
}
}
/*
* 导入前
* 参数一:项目的路径
* 参数二:项目的资源路径
*/
public async beforeImport(projectRoot: string, sourceFsPath: string): Promise<boolean> {
// 重置
this.reset();
//
this.sourceFsPath = sourceFsPath;
const info = ImporterBase.getPathInfo(projectRoot, sourceFsPath);
this.destFsPath = info.to;
this.destMetaFsPath = info.toMeta;
this.pathInfo = info.pathInfo;
// update asset meta
this._2dMeta = this.read2dMeta(sourceFsPath);
// 检查 uuid 是否冲突
const newUuid = this._2dMeta ? await this.checkUuid(this._2dMeta) : this.get3DUuid();
this._3dMeta = this.createNewMeta(newUuid);
this._3dMeta.uuid = newUuid;
this._2dMeta && importProjectAssets.set(this._2dMeta.uuid, {
type: extname(sourceFsPath),
basePath: sourceFsPath,
outPath: this.destMetaFsPath,
outUuid: this._2dMeta.uuid,
meta: this._2dMeta,
});
return true;
}
public needImport() {
let doImport = true;
if (existsSync(this.destFsPath) && existsSync(this.destMetaFsPath)) {
const meta = readJSONSync(this.destMetaFsPath);
doImport = this._3dMeta.uuid !== meta.uuid;
}
if (doImport) {
// console.log(Editor.I18n.t('plugin-import-2x.import_log', {
// path: this.sourceFsPath,
// }));
}
return doImport;
}
/*
* 导入并且进行转换
*/
public async import(main?: any): Promise<boolean> {
return true;
}
/*
* 转换后进行报错跟拷贝源文件的处理
*/
public async afterImport() {
this.copySync(this.sourceFsPath);
if (this._2dTo3dSource) {
try {
if (this.destFsPath.endsWith('.ts') ||
this.destFsPath.endsWith('.js') ||
this.destFsPath.endsWith('.plist') ||
this.destFsPath.endsWith('.effect')) {
writeFileSync(this.destFsPath, this._2dTo3dSource, { encoding: 'utf8' });
}
else {
writeJSONSync(this.destFsPath, this._2dTo3dSource, { spaces: 2 });
}
}
catch (e) { console.error(e); }
}
// console.log('保存:' + this.destFsPath);
this.saveMeta();
}
/*
* 创建新的 meta 对象
*/
public createNewMeta(uuid?: string) {
return {
uuid: uuid || '',
imported: false,
importer: '*',
files: [],
subMetas: {},
userData: {},
ver: '0.0.1',
};
}
/*
* 读取 meta
*/
public read2dMeta(sourceFsPath: string) {
try {
if (!sourceFsPath.endsWith('.meta')) {
sourceFsPath += '.meta';
}
if (!existsSync(sourceFsPath)) {
return null;
}
return readJSONSync(sourceFsPath);
}
catch (e) {
console.error(e);
return null;
}
}
/*
* 拷贝资源 + meta
*/
public copySync(from: string, to?: string) {
try {
if (!existsSync(from)) {
return 0;
}
if (to) {
ensureDirSync(dirname(to));
copyFileSync(from, to);
} else {
ensureDirSync(dirname(this.destFsPath));
copyFileSync(from, this.destFsPath);
}
}
catch (e) {
console.error(e);
}
}
/*
* 保存 meta
*/
public async saveMeta() {
if (!this.destMetaFsPath.endsWith('.meta')) {
this.destMetaFsPath += '.meta';
}
try {
writeJSONSync(this.destMetaFsPath, this._3dMeta, { spaces: 2 });
}
catch (e) {
console.error(e);
}
}
/*
* 保存
*/
public writeFileSync(to: string, data: any) {
try {
writeFileSync(to, data, { encoding: 'utf8' });
}
catch (e) {
console.error(e);
}
}
/*
* 加载源文件类型为 JSON
*/
public readJSONSync(sourceFsPath?: string) {
try {
return readJSONSync(sourceFsPath || this.sourceFsPath);
}
catch (e) {
console.error(e);
return null;
}
}
/*
* 加载源文件
*/
public readFileSync(sourceFsPath?: string) {
try {
return readFileSync(sourceFsPath || this.sourceFsPath, 'utf8');
}
catch (e) {
console.error(e);
return null;
}
}
/*
* 导入缓存纹理设置
*/
public async migratePlatformSettings(platformSettings: any) {
return await migratePlatformSettings(platformSettings);
}
static async getUuid(uuid: string, type?: string) {
if (UUID_2D_TO_3D.has(uuid)) {
return UUID_2D_TO_3D.get(uuid);
}
if (UUID_UI_2D_TO_3D.has(uuid)) {
return UUID_UI_2D_TO_3D.get(uuid);
}
if (UUID_SKIP_EFFECT.has(uuid)) {
console.warn(Editor.I18n.t('plugin-import-2x.effect_warn_tips', {
name: UUID_SKIP_EFFECT.get(uuid) as string,
}));
}
uuid = await ImporterBase.ensureDefaultAssets2DFor3D(uuid);
uuid = getNewUuid(uuid);
if (type && !uuid.includes('@')) {
const id = `@${ImporterBase.getNameByID(type)}`;
if (!uuid.endsWith(id)) {
uuid += id;
}
}
return uuid;
}
static getNewUuid(uuid: string) {
return getNewUuid(uuid);
}
/*
* 通过名字获取 id
*/
static getNameByID(name: string) {
return nameToId(name);
}
/*
* 创建默认资源
*/
static getDefaultAssets2D(uuid: string) {
return getDefaultAssets2D(uuid);
}
/*
* 创建 2d 默认资源
*/
static async ensureDefaultAssets2DFor3D(uuid: string) {
const subAssets = importSubAssets.get(uuid);
if (subAssets) {
uuid = subAssets.baseUuid;
}
else {
const projectAssets = importProjectAssets.get(uuid);
if (projectAssets) {
uuid = projectAssets.outUuid;
}
}
const info = getDefaultAssets2D(uuid);
if (info && info.path) {
// 如果是内置资源与 3d 的一致就直接用 3D 的
if (UUID_UI_2D_TO_3D.has(info.baseUuid)) {
return UUID_UI_2D_TO_3D.get(info.baseUuid);
}
if (UUID_2D_TO_3D.has(info.baseUuid)) {
return UUID_2D_TO_3D.get(info.baseUuid);
}
const defaultAssetsRootPath = join(__dirname, '../../static');
let relativePath = relative(defaultAssetsRootPath, info.path);
if (!relativePath.startsWith('assets')) {
relativePath = join('assets', relativePath);
}
const destFsPath = join(Editor.Project.path, relativePath);
try {
if (!existsSync(destFsPath)) {
if (destFsPath.endsWith('.mtl') || destFsPath.endsWith('.effect')) {
await import2DChunks(false);
}
const converter = getConverter(extname(info.path));
if (converter) {
await converter.beforeImport(defaultAssetsRootPath, info.path);
const isDone = await converter.import();
if (isDone) {
await converter.afterImport();
}
}
const readmePath = join(Editor.Project.path, 'asset', 'migrate-resources', 'README.md');
if (!existsSync(readmePath)) {
ensureDirSync(dirname(readmePath));
writeFileSync(readmePath, readFileSync(join(defaultAssetsRootPath, 'migrate-resources', 'README.md'), {encoding: 'utf8'}));
}
}
}
catch (e) {
console.error(e);
}
if (subAssets) {
return subAssets.uuid;
}
return info.baseUuid;
}
if (subAssets) {
return subAssets.uuid;
}
return uuid;
}
/*
* 通过 engine 进行序列化与反序列化
*/
async queryCCClass(engine: any, message: any) {
engine.contentWindow.postMessage(message, '*');
return new Promise((resolve, reject) => {
function onMessageCb(event: any) {
window.removeEventListener("message", onMessageCb, false);
resolve(event.data);
}
window.addEventListener("message", onMessageCb, false);
});
}
replaceScript(name: string) {
try {
const defaultAssetsRootPath = join(__dirname, '../../static/migrate-resources/default-assets-2d/scripts');
const fromFsPath = join(defaultAssetsRootPath, name);
const destFsPath = join(Editor.Project.path, 'assets', 'default-assets-2d', 'scripts', name);
if (!existsSync(destFsPath)) {
ensureDirSync(dirname(destFsPath));
copyFileSync(fromFsPath, destFsPath);
}
const fromMetaPath = fromFsPath + '.meta';
const destMetaFsPath = destFsPath + '.meta';
if (!existsSync(destMetaFsPath)) {
ensureDirSync(dirname(destMetaFsPath));
copyFileSync(fromMetaPath, destMetaFsPath);
}
const meta = readJSONSync(fromMetaPath);
// @ts-ignore
const EditorExtends: any = require('@base/electron-module').require('EditorExtends');
const UuidUtils = EditorExtends.UuidUtils;
return UuidUtils.compressUuid(meta.uuid, false);
}
catch (e) {
console.error(e);
return '';
}
}
//
ensureDefaultSprite2DFor3D(json3D: any) {
for (const key in json3D) {
const item = json3D[key];
if (item.__type__ === 'cc.StudioComponent') {
item.__type__ = this.replaceScript('studio-component.ts');
}
else if (item.__type__ === 'cc.StudioWidget') {
item.__type__ = this.replaceScript('studio-widget.ts');
}
}
return json3D;
}
}

View File

@@ -0,0 +1,127 @@
export const UUID_2D_TO_3D: Map<string, string> = new Map<string, string>();
export const UUID_UI_2D_TO_3D: Map<string, string> = new Map<string, string>();
export const UUID_SKIP_EFFECT: Map<string, string> = new Map<string, string>();
export function initDiff() {
// builtin-standard.effect -> builtin-phong.effect
UUID_2D_TO_3D.set('abc2cb62-7852-4525-a90d-d474487b88f2', '1baf0fc9-befa-459c-8bdd-af1a450a0319');
// builtin-2d-graphics -> builtin-graphics
UUID_2D_TO_3D.set('30682f87-9f0d-4f17-8a44-72863791461b', '1c02ae6f-4492-4915-b8f8-7492a3b1e4cd');
// builtin-2d-spine -> builtin-spine
UUID_2D_TO_3D.set('0e93aeaa-0b53-4e40-b8e0-6268b4e07bd7', '7383da24-dfde-48e8-82a7-a6e8a56f285c');
// builtin-2d-sprite -> builtin-sprite
UUID_2D_TO_3D.set('2874f8dd-416c-4440-81b7-555975426e93', '60f7195c-ec2a-45eb-ba94-8955f60e81d0');
// builtin-3d-particle -> builtin-particle
UUID_2D_TO_3D.set('829a282c-b049-4019-bd38-5ace8d8a6417', 'd1346436-ac96-4271-b863-1f4fdead95b0');
// builtin-3d-trail -> builtin-particle-trail
UUID_2D_TO_3D.set('2a7c0036-e0b3-4fe1-8998-89a54b8a2bec', '17debcc3-0a6b-4b8a-b00b-dc58b885581e');
// builtin-clear-stencil
UUID_2D_TO_3D.set('cf7e0bb8-a81c-44a9-ad79-d28d43991032', '810e96e4-e456-4468-9b59-f4e8f39732c0');
// builtin-unlit
UUID_2D_TO_3D.set('6d91e591-4ce0-465c-809f-610ec95019c6', 'a3cd009f-0ab0-420d-9278-b9fdab939bbc');
// builtin-toon
UUID_2D_TO_3D.set('e2f00085-c597-422d-9759-52c360279106', 'a7612b54-35e3-4238-a1a9-4a7b54635839');
// builtin-2d-sprite -> ui-sprite-material
UUID_2D_TO_3D.set('eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432', 'fda095cb-831d-4601-ad94-846013963de8');
// builtin-2d-label -> ui-sprite-material
UUID_2D_TO_3D.set('e02d87d4-e599-4d16-8001-e14891ac6506', 'fda095cb-831d-4601-ad94-846013963de8');
// builtin-2d-gray-sprite -> ui-sprite-material
UUID_2D_TO_3D.set('3a7bb79f-32fd-422e-ada2-96f518fed422', 'fda095cb-831d-4601-ad94-846013963de8');
// primitives
UUID_2D_TO_3D.set('954fec8b-cd16-4bb9-a3b7-7719660e7558', '1263d74c-8167-4928-91a6-4e2672411f47');
// box.mesh
UUID_2D_TO_3D.set('046f172c-1574-488b-bbb8-6415a9adb96d', '1263d74c-8167-4928-91a6-4e2672411f47@a804a');
// capsule.mesh
UUID_2D_TO_3D.set('83f5eff8-3385-4f95-9b76-8da0aa1d96cd', '1263d74c-8167-4928-91a6-4e2672411f47@801ec');
// cone.mesh
UUID_2D_TO_3D.set('7a17de6e-227a-46b1-8009-e7157d4d3acf', '1263d74c-8167-4928-91a6-4e2672411f47@38fd2');
// cylinder.mesh
UUID_2D_TO_3D.set('b430cea3-6ab3-4106-b073-26c698918edd', '1263d74c-8167-4928-91a6-4e2672411f47@8abdc');
// DefaultMaterial
UUID_2D_TO_3D.set('a5849239-3ad3-41d1-8ab4-ae9fea11f97f', '1263d74c-8167-4928-91a6-4e2672411f47@ea6e2');
// plane.mesh
UUID_2D_TO_3D.set('a1ef2fc9-9c57-418a-8f69-6bed9a7a0e7f', '1263d74c-8167-4928-91a6-4e2672411f47@2e76e');
// primitives.prefab
UUID_2D_TO_3D.set('ab2fdde9-10c2-44e4-bfe1-fcfcc1a86aa9', '1263d74c-8167-4928-91a6-4e2672411f47@aae0f');
// quad.mesh
UUID_2D_TO_3D.set('e93d3fa9-8c21-4375-8a21-14ba84066c77', '1263d74c-8167-4928-91a6-4e2672411f47@fc873');
// sphere.mesh
UUID_2D_TO_3D.set('3bbdb0f6-c5f6-45de-9f33-8b5cbafb4d6d', '1263d74c-8167-4928-91a6-4e2672411f47@17020');
// torus.mesh
UUID_2D_TO_3D.set('14c74869-bdb4-4f57-86d8-a7875de2be30', '1263d74c-8167-4928-91a6-4e2672411f47@40ece');
// box -> cube
UUID_2D_TO_3D.set('a87cc147-01b2-43f8-8e42-a7ca90b0c757', '30da77a1-f02d-4ede-aa56-403452ee7fde');
// capsule
UUID_2D_TO_3D.set('fe1417b6-fe6b-46a4-ae7c-9fd331f33a2a', '73ce1f7f-d1f4-4942-ad93-66ca3b3041ab');
// cone
UUID_2D_TO_3D.set('b5fc2cf2-7942-483d-be1f-bbeadc4714ad', '6350d660-e888-4acf-a552-f3b719ae9110');
// Cylinder
UUID_2D_TO_3D.set('1c5e4038-953a-44c2-b620-0bbfc6170477', 'ab3e16f9-671e-48a7-90b7-d0884d9cbb85');
// Plane
UUID_2D_TO_3D.set('3f376125-a699-40ca-ad05-04d662eaa1f2', '40563723-f8fc-4216-99ea-a81636435c10');
// Quad
UUID_2D_TO_3D.set('6c9ef10d-b479-420b-bfe6-39cdda6a8ae0', '34a07346-9f62-4a84-90ae-cb83f7a426c1');
// Sphere
UUID_2D_TO_3D.set('2d9a4b85-b0ab-4c46-84c5-18f393ab2058', '655c9519-1a37-472b-bae6-29fefac0b550');
// Torus
UUID_2D_TO_3D.set('de510076-056b-484f-b94c-83bef217d0e1', 'd47f5d5e-c931-4ff4-987b-cc818a728b82');
//
UUID_SKIP_EFFECT.set('abc2cb62-7852-4525-a90d-d474487b88f2', 'builtin-phong.effect');
// 内置 UI 替换
// default-particle.png
UUID_UI_2D_TO_3D.set('600301aa-3357-4a10-b086-84f011fa32ba', 'b5b27ab1-e740-4398-b407-848fc2b2c897');
// default_btn_disabled.png
UUID_UI_2D_TO_3D.set('71561142-4c83-4933-afca-cb7a17f67053', '951249e0-9f16-456d-8b85-a6ca954da16b');
// default_btn_normal.png
UUID_UI_2D_TO_3D.set('e851e89b-faa2-4484-bea6-5c01dd9f06e2', '20835ba4-6145-4fbc-a58a-051ce700aa3e');
// default_btn_pressed.png
UUID_UI_2D_TO_3D.set('b43ff3c2-02bb-4874-81f7-f2dea6970f18', '544e49d6-3f05-4fa8-9a9e-091f98fc2ce8');
// default_editbox_bg.png
UUID_UI_2D_TO_3D.set('edd215b9-2796-4a05-aaf5-81f96c9281ce', 'bd1bcaba-bd7d-4a71-b143-997c882383e4');
// default_panel.png
UUID_UI_2D_TO_3D.set('d81ec8ad-247c-4e62-aa3c-d35c4193c7af', 'b730527c-3233-41c2-aaf7-7cdab58f9749');
// default_progressbar.png
UUID_UI_2D_TO_3D.set('cfef78f1-c8df-49b7-8ed0-4c953ace2621', '24a704da-2867-446d-8d1a-5e920c75e09d');
// default_progressbar_bg.png
UUID_UI_2D_TO_3D.set('99170b0b-d210-46f1-b213-7d9e3f23098a', '9fd900dd-221b-4f89-8f2c-fba34243c835');
// default_radio_button_off.png
UUID_UI_2D_TO_3D.set('567dcd80-8bf4-4535-8a5a-313f1caf078a', 'f12a23c4-b924-4322-a260-3d982428f1e8');
// default_radio_button_on.png
UUID_UI_2D_TO_3D.set('9d60001f-b5f4-4726-a629-2659e3ded0b8', '45828f25-b50d-4c52-a591-e19491a62b8c');
// default_scrollbar.png
UUID_UI_2D_TO_3D.set('0291c134-b3da-4098-b7b5-e397edbe947f', '0da256a2-21f6-481b-90b6-d3643a09179b');
// default_scrollbar_bg.png
UUID_UI_2D_TO_3D.set('4bab67cb-18e6-4099-b840-355f0473f890', '28765e2f-040a-4c65-8e8c-f9d0bb79d863');
// default_scrollbar_vertical.png
UUID_UI_2D_TO_3D.set('d6d3ca85-4681-47c1-b5dd-d036a9d39ea2', 'afc47931-f066-46b0-90be-9fe61f213428');
// default_scrollbar_vertical_bg.png
UUID_UI_2D_TO_3D.set('617323dd-11f4-4dd3-8eec-0caf6b3b45b9', 'ffb88a8f-af62-48f4-8f1d-3cb606443a43');
// default_sprite.png
UUID_UI_2D_TO_3D.set('6e056173-d285-473c-b206-40a7fff5386e', '57520716-48c8-4a19-8acf-41c9f8777fb0');
// default_sprite_splash.png
UUID_UI_2D_TO_3D.set('0275e94c-56a7-410f-bd1a-fc7483f7d14a', '7d8f9b89-4fd1-4c9f-a3ab-38ec7cded7ca');
// default_toggle_checkmark.png
UUID_UI_2D_TO_3D.set('73a0903d-d80e-4e3c-aa67-f999543c08f5', '158e7e52-3220-4cd7-9694-713e0e6e8278');
// default_toggle_disabled.png
UUID_UI_2D_TO_3D.set('c25b9d50-c8fc-4d27-beeb-6e7c1f2e5c0f', 'ca7e121b-293c-4763-829a-b7a5fa81f0d2');
// default_toggle_normal.png
UUID_UI_2D_TO_3D.set('d29077ba-1627-4a72-9579-7b56a235340c', '11bdc4b0-64a8-4eb7-a2a7-9fb9e233e977');
// default_toggle_pressed.png
UUID_UI_2D_TO_3D.set('b181c1e4-0a72-4a91-bfb0-ae6f36ca60bd', 'a04e994f-ee49-47b6-9d08-2f59e3773fcc');
// atom
UUID_UI_2D_TO_3D.set('b8223619-7e38-47c4-841f-9160c232495a', '86f25d5c-9de5-454f-a5f9-ee16603e6701');
UUID_UI_2D_TO_3D.set('b2687ac4-099e-403c-a192-ff477686f4f5', '86f25d5c-9de5-454f-a5f9-ee16603e6701');
// atom.png texture
UUID_UI_2D_TO_3D.set('8a96b965-2dc0-4e03-aa90-3b79cb93b5b4', '24c419ea-63a8-4ea1-a9d0-7fc469489bbc@6c48a');
UUID_UI_2D_TO_3D.set('d0a82d39-bede-46c4-b698-c81ff0dedfff', '24c419ea-63a8-4ea1-a9d0-7fc469489bbc@6c48a');
// atom.png sprite-frame
UUID_UI_2D_TO_3D.set('bb42ed8e-0867-4584-ad63-b6f84f83bba8', '24c419ea-63a8-4ea1-a9d0-7fc469489bbc@f9941');
UUID_UI_2D_TO_3D.set('472df5d3-35e7-4184-9e6c-7f41bee65ee3', '24c419ea-63a8-4ea1-a9d0-7fc469489bbc@f9941');
// 3d 粒子
UUID_UI_2D_TO_3D.set('432fa09c-cf03-4cff-a186-982604408a07', 'ea7478b0-408d-4052-b703-f0d2355e095f');
// video
UUID_UI_2D_TO_3D.set('2be36297-9abb-4fee-8049-9ed5e271da8a', '2be36297-9abb-4fee-8049-9ed5e271da8a');
}

View File

@@ -0,0 +1,810 @@
import { readWriteFileByLineWithProcess } from "./utlis";
function getType(val: string) {
if (!isNaN(Number(val))) {
return undefined;
}
if (val === 'false' || val === 'true') {
return undefined;
}
if (val === 'null' || val === 'undefined') {
return undefined;
}
// 数组
if (val.startsWith('[') && val.endsWith(']')) {
return [];
}
val = val.split('(')[0];
if (!val.includes('cc')) {
return undefined;
}
if (val.startsWith('cc.')) {
const array = val.split('.');
if (array.length > 3) {
return array[1];
}
}
return val;
}
function getInfo(line: string, skip: boolean = false) {
let values = line.split(':');
if (values.length <= 1) {
// 函数
values = line.split('(');
}
if (values.length <= 1) {
return {
key: line,
value: line,
};
}
let value = values[1].trim().split(',')[0];
if (!skip) {
value = value.replace(/'|"|,/g, '');
}
return {
key: values[0].trim(),
value: value,
};
}
function syncIndex(line: string, index: number) {
let result = line.match(/\{/g);
if (result && result.length > 0) {
index += result.length;
}
result = line.match(/\}/g);
if (result && result.length > 0) {
index -= result.length;
}
return index;
}
function createContent(name: string) {
return {
name: name,
extends: '',
mixins: '',
editors: {},
statics: {},
properties: {},
functions: {},
};
}
export async function parseJSCode(path: string, name: string) {
let otherIndex = 0;
let classCodeIndex = 0;
const classCodeMap: Map<number, any> = new Map();
const importCodeMap: Map<number, string> = new Map();
const otherCodeMap: Map<number, string> = new Map();
const endCodeMap: Map<number, string> = new Map();
const ccKeys: string[] = [];
let openClass: boolean = false;
let classIndex = 0;
let openName: boolean | undefined = undefined;
let openExtends: boolean | undefined = undefined;
let openMixins: boolean | undefined = undefined;
let openEditors: boolean | undefined = undefined;
let propTotalIndex = 0;
let subPropName = '';
let subPropIndex = 0;
let hasGet: boolean | undefined = undefined;
let getIndex = 0;
let hasSet: boolean | undefined = undefined;
let setIndex = 0;
let hasNotify: boolean | undefined = undefined;
let notifyIndex = 0;
let openSubProp: boolean | undefined = undefined;
let openProperties: boolean | undefined = undefined;
let staticIndex = 0;
let subStaticIndex = 0;
let subStaticName: string | undefined = undefined;
let openStatics: boolean | undefined = undefined;
let funcName = '';
let funcIndex = 0;
let openFunctions: boolean | undefined = undefined;
let content: any;
let isSkips = false;
let topNote: string = '';
await readWriteFileByLineWithProcess(path, (line: string) => {
try {
// 剔除空格
let noTrimLine = line;
line = line.trim();
if (line.startsWith('/*')) {
isSkips = true;
topNote += (line + '\n');
return;
}
if (isSkips) {
isSkips = !line.endsWith('*/');
topNote += (line + '\n');
return;
}
// 直接过滤注释文字
if (line.startsWith('/') || line.startsWith('*') || !line) {
return;
}
if (!openClass) {
if (line.includes('cc.Class(')) {
openClass = true;
classIndex = 1;
classCodeIndex = classCodeMap.size;
content = createContent(name);
if (!classCodeMap.has(classCodeIndex)) {
classCodeMap.set(classCodeIndex, content);
}
} else {
if (line.includes('require')) {
importCodeMap.set(importCodeMap.size, line);
} else {
const ccKeyArr = line.match(/(?<=cc.)(.*?)(?=[.|,|;|)|}|(])/);
const ccKey = ccKeyArr && ccKeyArr[0];
if (ccKey) {
ccKeys.push(ccKey);
}
if (classCodeMap.size === 0) {
otherIndex = syncIndex(line, otherIndex);
if (line.includes('cc.runtime')) {
otherIndex -= 1;
noTrimLine = '//' + noTrimLine;
}
if (ccKey) {
if (ccKey.includes('=') || ccKey.includes('function')) {
noTrimLine = noTrimLine.replace(`cc.${ccKey}`, `const ${ccKey}`);
}
else {
noTrimLine = noTrimLine.replace(`cc.${ccKey}`, ccKey);
}
const multiple = noTrimLine.match(new RegExp(ccKey, 'g'));
if (multiple && multiple.length > 1) {
noTrimLine = '//' + noTrimLine;
}
}
otherCodeMap.set(otherCodeMap.size, noTrimLine);
} else if (classCodeMap.size > 0) {
otherIndex = syncIndex(line, otherIndex);
if (otherIndex < 0) {
noTrimLine = '//' + line;
}
endCodeMap.set(endCodeMap.size, noTrimLine);
}
}
}
} else if (openClass) {
// --------------- 检测是否解析类完毕 ---------------
classIndex = syncIndex(line, classIndex);
if (classIndex === 0 &&
(line.endsWith('});') || line.endsWith('})') || line.endsWith(');') || line.endsWith(')') || line.endsWith(';'))) {
openClass = false;
classCodeMap.set(classCodeIndex, content);
return;
}
if (openProperties === undefined && openFunctions === undefined && openStatics === undefined) {
// --------------- 获取 name ---------------
if (openName === undefined && line.startsWith('name:')) {
openName = true;
}
if (openName) {
content.name = getInfo(line).value;
if (line.endsWith(',')) {
openName = false;
}
return;
}
// --------------- 获取继承 ---------------
if (openExtends === undefined && line.startsWith('extends:')) {
openExtends = true;
}
if (openExtends) {
content.extends = getInfo(line).value;
if (line.endsWith(',')) {
openExtends = false;
}
return;
}
// --------------- 获取 mixins ---------------
if (openMixins === undefined && line.startsWith('mixins:')) {
openMixins = true;
}
if (openMixins) {
content.mixins = getInfo(line).value;
if (line.endsWith(',')) {
openMixins = false;
}
return;
}
// --------------- 获取 editor ---------------
if (openEditors === undefined && line.startsWith('editor:')) {
openEditors = true;
return;
}
if (openEditors) {
if (line.endsWith('},')) {
openEditors = false;
return;
}
const info = getInfo(line);
content.editors[info.key] = info.value;
return;
}
}
// --------------- 获取 properties ---------------
if (openProperties === undefined && line.startsWith('properties:')) {
propTotalIndex = syncIndex(line, propTotalIndex);
if (propTotalIndex === 0) {
return;
}
openProperties = true;
return;
}
if (openProperties) {
if (openSubProp === undefined && line.includes('{')) {
subPropIndex = syncIndex(line, subPropIndex);
const info = getInfo(line);
subPropName = info.key;
content.properties[subPropName] = {
hasGet: undefined,
hasSet: undefined,
notify: undefined,
type: undefined,
default: undefined,
visible: undefined,
serializable: undefined,
content: '',
};
if (subPropIndex === 0) {
content.properties[subPropName].content = line;
return;
}
openSubProp = true;
return;
}
if (openSubProp) {
subPropIndex = syncIndex(line, subPropIndex);
if (subPropIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
openSubProp = undefined;
return;
}
const subProp = content.properties[subPropName];
subProp.content += (line + '\n');
if (hasGet === undefined && line.includes('get:')) {
getIndex = syncIndex(line, getIndex);
if (getIndex === 0) {
subProp.hasGet = noTrimLine + '\n';
return;
}
subProp.hasGet = ` get ${subPropName} () {\n`;
hasGet = true;
return;
}
if (hasGet) {
getIndex = syncIndex(line, getIndex);
if (getIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
hasGet = undefined;
subProp.hasGet += ' }';
return;
} else {
subProp.hasGet += ' ' + noTrimLine.substring(noTrimLine.search(/\S/), noTrimLine.length) + '\n';
}
return;
}
if (hasSet === undefined && line.includes('set:')) {
setIndex = syncIndex(line, setIndex);
let params = line.match(/(?<=\()(.*)(?=\))/);
params = params ? params[0].split(',') : [];
let str = '';
for (let i = 0; i < params.length; ++i) {
const param = params[i].trim();
if (param === '') {
continue;
}
if (i > 0) {
str += ' ';
}
str += `${param}: any`;
if (i < params.length - 1) {
str += ',';
}
}
if (setIndex === 0) {
subProp.hasSet = noTrimLine + '\n';
return;
}
subProp.hasSet = ` set ${subPropName} (${str}) {\n`;
hasSet = true;
return;
}
if (hasSet) {
setIndex = syncIndex(line, setIndex);
if (setIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
hasSet = undefined;
subProp.hasSet += ' }';
return;
} else {
subProp.hasSet += ' ' + noTrimLine.substring(noTrimLine.search(/\S/), noTrimLine.length) + '\n';
}
return;
}
if (hasNotify === undefined && line.includes('notify')) {
notifyIndex = syncIndex(line, notifyIndex);
if (notifyIndex === 0) {
subProp.notify = noTrimLine + '\n';
return;
}
hasNotify = true;
subProp.notify = line + '\n';
return;
}
if (hasNotify) {
notifyIndex = syncIndex(line, notifyIndex);
if (notifyIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
hasNotify = undefined;
subProp.notify += '}';
} else {
subProp.notify += line + '\n';
}
return;
}
const info = getInfo(line);
if (subProp.default === undefined && line.includes('default:')) {
subProp.default = info.value;
}
if (subProp.type === undefined && line.includes('type:')) {
subProp.type = info.value;
}
if (subProp.visible === undefined && line.includes('visible:')) {
subProp.visible = info.value;
}
if (subProp.serializable === undefined && line.includes('serializable:')) {
subProp.serializable = info.value;
}
} else {
propTotalIndex = syncIndex(line, propTotalIndex);
if (propTotalIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
openProperties = undefined;
return;
}
const info = getInfo(line);
let type = getType(info.value);
if (Array.isArray(type)) {
if (info.value.length > 2) {
type = 'array:' + info.value.substring(1, info.value.length - 1);
}
else {
type = undefined;
}
}
const value = info.value;
content.properties[info.key] = {
hasGet: undefined,
hasSet: undefined,
notify: undefined,
type: type,
default: value,
visible: undefined,
serializable: undefined,
content: line,
};
}
return;
}
// --------------- 获取 statics ---------------
if (openStatics === undefined && line.startsWith('statics:')) {
staticIndex = syncIndex(line, staticIndex);
if (staticIndex === 0) {
return;
}
openStatics = true;
return;
}
if (openStatics) {
staticIndex = syncIndex(line, staticIndex);
if (staticIndex === 0 && (line.endsWith('},') || line.endsWith(','))) {
openStatics = false;
subStaticName = undefined;
return;
}
if (subStaticName === undefined && line.includes('function')) {
const info = getInfo(line);
subStaticIndex = syncIndex(line, subStaticIndex);
let params = line.match(/(?<=\()(.*)(?=\))/);
params = params ? params[0].split(',') : [];
let str = '';
for (let i = 0; i < params.length; ++i) {
const param = params[i].trim();
if (param === '') {
continue;
}
if (i > 0) {
str += ' ';
}
str += `${param}: any`;
if (i < params.length - 1) {
str += ',';
}
}
if (subStaticIndex === 0) {
content.statics[info.key] = {
parameter: '',
content: noTrimLine + '\n',
};
return;
}
subStaticName = info.key;
content.statics[subStaticName] = {
parameter: params ? params[0] : '',
content: ` public static ${subPropName} (${str}) {\n`,
};
return;
}
if (subStaticName !== undefined) {
subStaticIndex = syncIndex(line, subStaticIndex);
if (subStaticIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
content.statics[subStaticName].content += ' }';
return;
}
content.statics[subStaticName].content += (' ' + noTrimLine.substring(noTrimLine.search(/\S/), noTrimLine.length) + '\n');
} else {
const info = getInfo(line);
content.statics[info.key] = {
parameter: '',
content: ` public static ${info.key} = ${info.value};\n`,
};
}
return;
}
// --------------- 获取函数 ---------------
if (openFunctions === undefined) {
const info = getInfo(line);
funcName = info.key;
let params = line.match(/(?<=\()(.*)(?=\))/);
if (params) {
params = params[0].split(',');
let str = '';
for (let i = 0; i < params.length; ++i) {
const param = params[i].trim();
if (param === '') {
continue;
}
if (i > 0) {
str += ' ';
}
str += `${param}: any`;
if (i < params.length - 1) {
str += ',';
}
}
content.functions[funcName] = {
parameter: params ? params[0] : '',
content: ` ${funcName} (${str}) {\n`,
};
funcIndex = syncIndex(line, funcIndex);
if (funcIndex === 0) {
content.functions[funcName].content += ' }\n\n';
return;
}
openFunctions = true;
} else {
content.properties[info.key] = {
hasGet: undefined,
hasSet: undefined,
notify: undefined,
type: undefined,
default: info.value,
visible: undefined,
serializable: undefined,
content: line,
};
return;
}
return;
}
if (openFunctions) {
funcIndex = syncIndex(line, funcIndex);
if (funcIndex === 0 && (line.endsWith('},') || line.endsWith('}'))) {
openFunctions = undefined;
content.functions[funcName].content += ' }\n\n';
return;
}
const func = content.functions[funcName];
const len = noTrimLine.search(/\S/);
func.content += `${noTrimLine.substring(0, len)}// ${noTrimLine.substring(len, noTrimLine.length)} \n`;
return;
}
}
}
catch (e) {
console.error(e);
}
});
return {
topNote,
ccKeys,
classCodeMap,
importCodeMap,
otherCodeMap,
endCodeMap,
};
}
function match(line: string, regExpStr: string, global: string = '') {
try {
const regExp = new RegExp(`(?<=${regExpStr})([a-zA-Z0-9]+)`, global);
const result = line.match(regExp);
if (result) {
if (!global) {
return result[result.length - 1];
} else {
let arr: string[] = [];
for (let element of result) {
arr.push(element);
}
return arr;
}
}
}
catch (e) {
console.error(e);
}
return null;
}
function getRegExp(str: string, global: string = '') {
return new RegExp(str, global);
}
function addCode(content: string, code: string, enter: boolean = true) {
if (code) {
content += code;
if (enter) {
content += '\n';
}
}
return content;
}
const RENAME_COMPONENT: any = {
'BoxCollider': 'BoxCollider2D',
'BoxCollider3D': 'BoxCollider',
'CircleCollider': 'CircleCollider2D',
'Collider': 'Collider2D',
'Collider3D': 'Collider',
'DistanceJoint': 'DistanceJoint2D',
'ClickEvent': 'EventHandler',
'MouseJoint': 'MouseJoint2D',
'WheelJoint': 'WheelJoint2D',
'PolygonCollider': 'PolygonCollider2D',
'ParticleSystem': 'ParticleSystem2D',
'ParticleSystem3D': 'ParticleSystem',
'Joint': 'Joint2D',
'RigidBody': 'RigidBody2D',
'RigidBody3D': 'RigidBody',
'SphereCollider3D': 'SphereCollider',
'RenderComponent': 'UIRenderable',
'SkeletonAnimation': 'SkeletalAnimation',
'Float': 'CCFloat',
'string': 'CCString',
'Boolean': 'CCBoolean',
'Integer': 'CCInteger',
};
export async function parseTSCode(baseClassName: string, path: string) {
let isTop = true;
let isOther = false;
let topCode = '';
let imports: string[] = ['_decorator'];
let decoratorCode = '';
let otherImportCode = '';
let otherDecoratorCode = '';
let cccclassCode = '';
let contentCode = '';
let openClass = false;
let waitOpenClass = false;// 需要检测到 { 才能开启 openClass
let classIndex = 0;
let openFunctions: boolean | undefined = undefined;
let funcIndex = 0;
let openConstructor: boolean | undefined = undefined;
let constructorIndex = 0;
function pushImports(name: string) {
name = RENAME_COMPONENT[name] || name;
if (!imports.includes(name)) {
imports.push(name);
}
return name;
}
function replaceCodeByClassName(line: string, noTrimLine: string, isFunc?: boolean) {
let classNames;
if (isFunc) {
// 删除只需要判断是否是 cc.xx
classNames = match(line, '\:? (cc\.)', 'g');
}
else {
classNames = match(line, '\:? ?(cc\.)', 'g');
}
if (classNames) {
let newline = noTrimLine;
for (let className of classNames) {
let newClassName = pushImports(className);
let RegExp = getRegExp(`cc.${className}`, 'g');
if (noTrimLine.trim().replace(/ /g, '').includes(`${className}=null`)) {
newline = noTrimLine.replace(RegExp, `${newClassName} | null`);
} else {
newline = noTrimLine.replace(RegExp, newClassName);
}
}
let matchArr = newline.match(/([a-zA-Z0-9]+)? =? ([a-zA-Z0-9]+)/);
if (matchArr && matchArr[1] !== undefined && (matchArr[1] === matchArr[2])) {
return undefined;
}
return newline + '\n';
} else if (noTrimLine) {
return noTrimLine + '\n';
}
}
await readWriteFileByLineWithProcess(path, (line: string) => {
try {
// 剔除空格
let noTrimLine = line;
line = line.trim();
if (!openClass) {
if (line.includes('export default class ') || line.includes('export class ') || waitOpenClass) {
// const name = match(line, 'class? ') as string;
// if (name) {
// line = line.replace(name, baseClassName);
// }
let extend = match(line, 'extends ?(cc\.)') as string;
if (extend) {
let newExtend = pushImports(extend);
contentCode += line.replace(`cc.${extend}`, newExtend);
}
else {
contentCode += line;
}
contentCode += '\n';
classIndex = syncIndex(line, classIndex);
if (classIndex === 0) {
waitOpenClass = true;
return;
}
waitOpenClass = false;
openClass = true;
}
else {
// 直接过滤注释文字
if (line.startsWith('/') || line.startsWith('*')) {
if (isTop) {
topCode += '// ' + noTrimLine + '\n';
}
else if (isOther) {
otherImportCode += '// ' + noTrimLine + '\n';
}
}
else if (line.includes('cc._decorator')) {
isTop = false;
decoratorCode += noTrimLine.replace(/cc._decorator/, '_decorator') + '\n';
}
else if (line.includes('@ccclass')) {
cccclassCode = noTrimLine;
}
else if (line.startsWith('@')) {
otherDecoratorCode += noTrimLine;
}
else {
isOther = true;
if (!line) {
return;
}
let newline = replaceCodeByClassName(line, noTrimLine);
if (newline === undefined) {
otherImportCode += `// ${noTrimLine}\n`;
}
else if (newline) {
otherImportCode += newline;
}
}
}
}
else {
// 直接过滤注释文字
if (line.startsWith('/') || line.startsWith('*')) {
contentCode += '//' + noTrimLine + '\n';
return;
}
// --------------- 检测是否解析类完毕 ---------------
classIndex = syncIndex(line, classIndex);
if (classIndex === 0 && (line.endsWith('}') || line.endsWith('};'))) {
contentCode += '}\n\n';
openClass = false;
return;
}
if (openFunctions === undefined) {
let newline = replaceCodeByClassName(line, noTrimLine);
if (newline !== undefined) {
if ((line.includes('constructor ()') || line.includes('constructor()'))) {
openConstructor = true;
}
contentCode += newline;
}
}
// 函数
if (openFunctions === undefined && line.match(/(?<=\()(.*)(?=\))/)) {
funcIndex = syncIndex(line, funcIndex);
if (funcIndex === 0) {
return;
}
openFunctions = true;
return;
}
if (openFunctions) {
funcIndex = syncIndex(line, funcIndex);
if (funcIndex === 0 && line.endsWith('}')) {
contentCode += noTrimLine;
contentCode += '\n';
openFunctions = undefined;
return;
}
if (line) {
if (openConstructor && line.startsWith('super();')) {
contentCode += ' ' + line;
}
else {
contentCode += ' // ' + line;
}
}
else {
contentCode += line;
}
contentCode += '\n';
return;
}
}
}
catch (e) {
console.error(e);
}
});
let content = '';
content = addCode(content, topCode);
let importCode = `import { `;
for (let i = 0; i < imports.length; ++i) {
importCode += imports[i];
if (i < imports.length - 1) {
importCode += ', ';
}
}
importCode += ` } from 'cc';`;
content = addCode(content, importCode);
content = addCode(content, decoratorCode);
content = addCode(content, otherImportCode);
content = addCode(content, cccclassCode.replace(/@ccclass/, `@ccclass('${baseClassName}')`));
content = addCode(content, otherDecoratorCode);
// content = addCode(content, exportClassCode);
content = addCode(content, contentCode);
return {
content: content,
};
}

View File

@@ -0,0 +1,846 @@
'use strict';
// @ts-ignore
import { readdirSync, statSync, readJSONSync, readFileSync, ensureDirSync, copyFileSync, existsSync } from 'fs-extra';
import { join, dirname, basename, relative, extname } from 'path';
import { ImporterBase } from "./base";
import { createReadStream } from 'fs';
import { createInterface } from 'readline';
// @ts-ignore
import { DOMParser } from "xmldom";
import { createHash } from 'crypto';
const lodash = require('lodash');
export const SKIPS_SCRIPT = [
'use_reversed_rotateBy.js',
'use_reversed_rotateTo.js',
'use_v2.0.x_cc.Toggle_event.js',
'use_v2.1-2.2.1_cc.Toggle_event.js',
];
// 优化原本的 localeCompare 方法性能提升1000 空节点 1103ms -> 31ms
export const collator = new Intl.Collator('en', {
numeric: true,
sensitivity: 'base',
});
// 2d 的分组
export let layerToGroupMap: Map<number, number> = new Map();
let groupList: string[] = [];
export async function initGroupList(path: string) {
try {
const project = readJSONSync(join(path, '../settings/project.json'));
groupList = project['group-list'] || [];
for (let i = 0; i < groupList.length; ++i) {
let group = groupList[i];
await setGroupLayerByIndex(i, group);
}
}
catch (e) {
groupList = [];
// console.log(e);
}
}
export async function getGroupLayerByIndex(index: number) {
if (groupList.length === 0) {
return 1 << 25; // 默认为 UI_2D
}
const group = groupList[index];
if (group) {
return await setGroupLayerByIndex(index, group);
}
else {
return null;
}
}
export async function setGroupLayerByIndex(index: number, group: string) {
let userLayers = await Editor.Profile.getProject('project', 'layer');
let layer;
if (!userLayers) {
userLayers = [];
} else {
if (group === 'default') {
group = 'Default';
}
layer = userLayers.find((layer: any) => layer.name === group);
}
if (!layer) {
const length = userLayers.length;
layer = {
name: group,
value: (1 << length),
};
userLayers.push(layer);
// console.log('layer: ' + layer.name + ' ' + layer.value);
let key = 1 << index;
layerToGroupMap.set(key, layer.value);
await Editor.Profile.setProject('project', 'layer', userLayers);
}
return layer.value;
}
// 替换 fbx sub meta 中的 uuid
export const replaceFbxUuidMap: Map<string, any> = new Map<string, any>();
// 存储导入项目所有资源
export const importProjectAssets: Map<string, any> = new Map<string, any>();
export const importSubAssets: Map<string, any> = new Map<string, any>();
// 存储 uuid 列表,处理 uuid 冲突,确保 uuid 都是唯一的
export const uuidList: Map<string, string> = new Map<string, string>();
// 脚本名
export const scriptList: Map<string, string> = new Map<string, string>();
export let replaceScriptList = [];
export function updateReplaceScriptList(list: []) {
replaceScriptList = list;
}
export function saveUuid(oldUuid: string, newUuid: string) {
uuidList.set(oldUuid, newUuid);
}
export function getNewUuid(oldUuid: string) {
let uuid = uuidList.get(oldUuid);
if (!uuid) {
let info = importProjectAssets.get(oldUuid);
if (info && info.outUuid) {
uuid = info.outUuid;
}
if (!uuid) {
info = importSubAssets.get(oldUuid);
if (info && info.uuid) {
uuid = info.uuid;
}
}
}
return uuid || oldUuid;
}
export function clear() {
scriptList.clear();
replaceScriptList.length = 0;
replaceFbxUuidMap.clear();
}
// 存储 2D 默认资源的信息列表
interface DefaultAssets2D {
type: string,
path: string,
baseUuid: string,
}
const defaultAssets2DList: Map<string, DefaultAssets2D> = new Map<string, DefaultAssets2D>();
export function getDefaultAssets2D(uuid: string) {
return defaultAssets2DList.get(uuid);
}
export function scanningDefaultAssets2D() {
const default_asset_root = join(__dirname, '../../static');
const rootPath = join(__dirname, '../../static/migrate-resources/default-assets-2d');
defaultAssets2DList.clear();
function step(path: string) {
try {
if (path.endsWith('.DS_Store')) {
return;
}
const stat = statSync(path);
if (stat.isDirectory()) {
const names = readdirSync(path);
names.forEach((name: string) => {
const tempPath = join(path, name);
if (name.endsWith('.meta')) {
addImportProjectAssets(default_asset_root, tempPath, true);
return;
}
step(tempPath);
});
}
else {
const metaPath = join(dirname(path), basename(path) + '.meta');
const meta = readJSONSync(metaPath);
defaultAssets2DList.set(meta.uuid, {
path: path,
type: meta.type,
baseUuid: meta.uuid,
});
for (const key in meta.subMetas) {
const subMeta = meta.subMetas[key];
if (subMeta) {
defaultAssets2DList.set(subMeta.uuid, {
path: path,
type: meta.type,
baseUuid: meta.uuid,
});
}
}
}
} catch(error) {
console.error(error);
}
}
step(rootPath);
}
export function isFbxMultKey(subMetas: any, key: string) {
if (key.includes('-')) {
const elements = key.split('-');
const modeName = elements[0];
const keys = Object.keys(subMetas).map((key) => {
return key.includes(modeName + '-');
}).filter(Boolean);
return keys.length > 1;
}
return false;
}
export function addImportProjectAssets(root: string, path: string, isDefaultAssets: boolean = false) {
try {
let base = path.replace('.meta', '');
if (base.endsWith('.fire')) {
base = base.replace(/.fire+$/g, '.scene');
} else if (base.endsWith('.js')) {
base = base.replace(/.js+$/g, '.ts');
}
const meta = readJSONSync(path);
let outPath;
if (isDefaultAssets) {
outPath = join(Editor.Project.path, 'assets', relative(root, base + '.meta'));
}
else {
outPath = join(Editor.Project.path, relative(root, base + '.meta'));
}
if (existsSync(outPath)) {
const type = extname(base);
if (type === '.fbx' || type === '.FBX') {
for (let key in meta.subMetas) {
const subMeta = meta.subMetas[key];
if (subMeta) {
const isMult = isFbxMultKey(meta.subMetas, key);
// console.log('修改前:' + key + ' ' + isMult);
key = getFBXSubMetaNewName(path.replace('.meta', ''), key, isMult);
// console.log('修改后:' + key + ' ' + ImporterBase.getNameByID(key));
importSubAssets.set(subMeta.uuid, {
baseUuid: meta.uuid,
uuid: `${meta.uuid}@${ImporterBase.getNameByID(key)}`,
});
}
}
}
else if (meta.type === 'Texture Packer') {
for (let key in meta.subMetas) {
const subMeta = meta.subMetas[key];
if (subMeta) {
key = basename(key, extname(key));
importSubAssets.set(subMeta.uuid, {
baseUuid: meta.uuid,
uuid: `${meta.uuid}@${ImporterBase.getNameByID(key)}`,
});
}
}
}
else if (meta.type === 'sprite') {
for (const key in meta.subMetas) {
const subMeta = meta.subMetas[key];
if (subMeta) {
importSubAssets.set(subMeta.uuid, {
baseUuid: meta.uuid,
uuid: `${meta.uuid}@${ImporterBase.getNameByID('spriteFrame')}`,
});
}
}
}
else if (meta.type === 'raw') {
importSubAssets.set(meta.uuid, {
baseUuid: meta.uuid,
uuid: `${meta.uuid}@${ImporterBase.getNameByID('texture')}`,
});
}
importProjectAssets.set(meta.uuid, {
type: type,
basePath: path,
outPath: outPath,
outUuid: meta.uuid,
meta: meta,
});
}
}
catch (e) {}
}
export let chunksCacheBy2D: Map<string, any> = new Map<string, any>();
const getChunks = (path: string, regexp: RegExp, extname: string) => {
const chunksMap: Map<string, any> = new Map<string, any>();
function step(dir: string) {
const names = readdirSync(dir);
names.forEach((name: string) => {
const file = join(dir, name);
if (regexp.test(name)) {
const name = basename(file, extname);
const content = readFileSync(file, { encoding: 'utf8' });
chunksMap.set(name, {
from: file, // 源文件
to: join(Editor.Project.path, 'assets', 'migrate-resources', 'chunks', name + '.chunk'), // 导入到项目到
content: content,
getIncludePath(effectPath: string) {
return relative(effectPath, this.to);
},
});
} else if (statSync(file).isDirectory()) {
step(file);
}
});
}
step(path);
return chunksMap;
};
export function init2DChunks() {
chunksCacheBy2D = getChunks(join(__dirname, '../../static/migrate-resources/chunks'), /\.inc$/, '.inc');
}
export function import2DChunks(noRefres?: boolean): Promise<boolean> {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve) => {
let idx = 0;
let open = false;
for (const [key, value] of chunksCacheBy2D) {
if (!existsSync(value.to)) {
// console.log('导入:' + value.to);
ensureDirSync(dirname(value.to));
copyFileSync(value.from, value.to);
open = true;
}
idx++;
if (idx >= chunksCacheBy2D.size) {
if (open && !noRefres) {
await Editor.Message.request('asset-db', 'refresh-asset', 'db://assets');
}
resolve(true);
}
}
});
}
/* comporess texture */
const migrateMap: Record<string, string> = {
pvrtc_4bits: 'pvrtc_4bits_rgba',
pvrtc_2bits: 'pvrtc_2bits_rgba',
etc2: 'etc2_rgba',
etc1: 'etc1_rgb_a',
};
const PLATFORMS = ['miniGame', 'web', 'android', 'ios', 'pc'];
export async function migratePlatformSettings(platformSettings: any) {
if (!platformSettings || Object.keys(platformSettings).length === 0) {
return;
}
const result = {
useCompressTexture: true,
presetId: '',
};
if (platformSettings.default && Object.keys(platformSettings).length === 1) {
// 只有默认配置需要全部平台都配一遍
PLATFORMS.forEach((platformType) => {
const config = {};
platformSettings.default.formats.forEach((format: any) => {
// @ts-ignore
config[format.name] = format.quality;
});
platformSettings[platformType] = config;
});
} else {
Object.keys(platformSettings).forEach((platformType) => {
if (platformType === 'default') {
return;
}
if (platformType !== 'default') {
const defaultConfig: any = {};
if (platformSettings.default) {
const defaultData = JSON.parse(JSON.stringify(platformSettings.default));
if (defaultData.formats) {
defaultData.formats.forEach((format: any) => {
defaultConfig[format.name] = format.quality;
});
}
}
const otherConfig: any = {};
platformSettings[platformType].formats.forEach((format: any) => {
otherConfig[format.name] = format.quality;
});
platformSettings[platformType] = Object.assign(defaultConfig, otherConfig);
}
migrateCompressTextureType(platformSettings[platformType]);
if (platformType === 'minigame') {
platformSettings.miniGame = platformSettings.minigame;
delete platformSettings.minigame;
}
});
}
delete platformSettings.default;
if (Object.keys(platformSettings).length === 0) {
return;
}
result.presetId = await getPresetId(platformSettings);
return result;
}
function migrateCompressTextureType(config: any) {
if (!config) {
return;
}
Object.keys(config).forEach((name: string) => {
if (!migrateMap[name]) {
return;
}
config[migrateMap[name]] = config[name];
delete config[name];
});
}
async function getPresetId(platformSettings: any) {
const presetId = 'presetId' + Date.now();
// @ts-ignore
let userPreset = await Editor.Profile.getProject('builder', 'textureCompressConfig.userPreset');
if (!userPreset) {
userPreset = {
[presetId]: {
name: presetId,
options: platformSettings,
},
};
// @ts-ignore
await Editor.Profile.setProject('builder', `textureCompressConfig.userPreset`, userPreset);
return presetId;
}
for (const Id of Object.keys(userPreset)) {
if (lodash.isEqual(userPreset[Id].options, platformSettings)) {
return Id;
}
}
// @ts-ignore
await Editor.Profile.setProject('builder', `textureCompressConfig.userPreset.${presetId}`, {
name: presetId,
options: platformSettings,
});
return presetId;
}
export function getBlendFactor2DTo3D(value: number) {
switch (value) {
case 0: // ZERO
return 0;
case 1: // ONE
return 1;
case 0x302:// SRC_ALPHA
return 2;
case 0x304:// DST_ALPHA
return 3;
case 0x303:// ONE_MINUS_SRC_ALPHA
return 4;
case 0x305:// ONE_MINUS_DST_ALPHA
return 5;
case 0x300:// SRC_COLOR
return 6;
case 0x306:// DST_COLOR
return 7;
case 0x301:// ONE_MINUS_SRC_COLOR
return 8;
case 0x307:// ONE_MINUS_DST_COLOR
return 9;
}
return value;
}
export function hasComponent(target: any, json3D: any, type: string) {
for (const component of target._components) {
const id = component.__id__;
if (json3D[id].__type__ === type) {
return true;
}
}
return false;
}
const UI_COMPONENT = [
'cc.Canvas',
'cc.Widget',
'cc.Sprite',
'cc.Label',
'cc.LabelOutline',
'cc.LabelShadow',
'cc.RichText',
'cc.ParticleSystem',
'cc.TiledMap',
'cc.TiledTile',
'cc.TiledLayer',
'cc.TiledObjectGroup',
'cc.Layout',
'cc.Button',
'cc.ScrollView',
'cc.Slider',
'cc.PageView',
'cc.ProgressBar',
'cc.Toggle',
'cc.ToggleContainer',
'cc.ToggleGroup',
'cc.EditBox',
'cc.VideoPlayer',
'cc.WebView',
'cc.UITransform',
'cc.UIOpacity',
'sp.Skeleton',
'dragonBones.ArmatureDisplay',
];
export function hasUIRenderComponent(target: any, json: any) {
if (!target._is3DNode) {
return true;
}
// 如果是自动同步的 prefab 是没有 _components 的
if (!target._components) {
return false;
}
for (const componentData of target._components) {
const id = componentData.__id__;
const component = json[id];
if (component) {
const __type__ = component.__type__;
if (UI_COMPONENT.includes(__type__)) {
return true;
}
}
}
for (const childData of target._children) {
const id = childData.__id__;
const child = json[id];
if (hasUIRenderComponent(child, json)) {
return true;
}
}
return false;
}
export function hasCanvasComponent(node: any, json2D: any) {
for (const componentData of node._components) {
const id = componentData.__id__;
const component = json2D[id];
if (component) {
const __type__ = component.__type__;
if (__type__ === 'cc.Canvas') {
return true;
}
}
}
return false;
}
export function setColor(uiComponent: any, nodeID: any, json2D: any) {
if (nodeID) {
const node = json2D[nodeID];
if (node && node._color) {
uiComponent._color.r = node._color.r;
uiComponent._color.g = node._color.g;
uiComponent._color.b = node._color.b;
}
}
}
export function getFBXSubMetaNewName(fsPath: string, baseName: string, isMult: boolean) {
let ext = extname(baseName);
const elements = baseName.split('-');
let name = elements && elements[0];
const modelName = basename(fsPath, extname(fsPath));
if (name && (modelName === name)) {
switch (ext) {
case '.sac':
name = `UnnamedAnimation`;
break;
case '.image':
name = `UnnamedImage`;
break;
case '.mesh':
name = `UnnamedMesh`;
break;
case '.mtl':
name = `UnnamedMaterial`;
break;
case '.skeleton':
name = `UnnamedSkeleton`;
break;
case '.texture':
name = `UnnamedTexture`;
break;
default:
name = `Unnamed`;
}
if (isMult) {
name = name + '-' + elements[1];
}
}
name = name.replace(ext, '');
switch (ext) {
case '.sac':
ext = '.animation';
break;
case '.mtl':
ext = '.material';
break;
}
return name + ext;
}
export async function readWriteFileByLineWithProcess(readName: any, callback: any) {
await new Promise((resolve) => {
const readStream = createReadStream(readName);
const readLine = createInterface({
input: readStream,
});
readLine.on('line', (line: string) => {
callback(line);
});
readLine.on('close', () => {
resolve(true);
});
});
}
/**
* 读取 tmx 文件内容,查找依赖的 texture 文件信息
* @param tmxFile tmx 文件路径
* @param tmxFileData tmx 文件内容
* @returns imageFullPaths
*/
export async function searchTmxDependImages(tmxFile: string, tmxFileData: string) {
// 读取 xml 数据
const doc = new DOMParser().parseFromString(tmxFileData);
if (!doc) {
console.error(`TiledMap import failed: failed to parser ${tmxFile}`);
return;
}
let imgFullPaths: string[] = [];
const rootElement = doc.documentElement;
const tilesetElements = rootElement.getElementsByTagName('tileset');
// 读取内部的 source 数据
for (let i = 0; i < tilesetElements.length; i++) {
const tileset = tilesetElements[i];
const sourceTSXAttr = tileset.getAttribute('source');
if (sourceTSXAttr) {
// 获取 texture 路径
const tsxAbsPath = join(dirname(tmxFile), sourceTSXAttr);
if (existsSync(tsxAbsPath)) {
const tsxContent = readFileSync(tsxAbsPath, 'utf-8');
const tsxDoc = new DOMParser().parseFromString(tsxContent);
if (tsxDoc) {
const imageFullPath = await parseTilesetImages(tsxDoc, tsxAbsPath);
imgFullPaths = imgFullPaths.concat(imageFullPath);
} else {
console.warn('Parse %s failed.', tsxAbsPath);
}
}
}
// import images
const imageFullPath = await parseTilesetImages(tileset, tmxFile);
imgFullPaths = imgFullPaths.concat(imageFullPath);
}
const imageLayerTextures: string[] = [];
const imageLayerElements = rootElement.getElementsByTagName('imagelayer');
for (let ii = 0, nn = imageLayerElements.length; ii < nn; ii++) {
const imageLayer = imageLayerElements[ii];
const imageInfos = imageLayer.getElementsByTagName('image');
if (imageInfos && imageInfos.length > 0) {
const imageInfo = imageInfos[0];
const imageSource = imageInfo.getAttribute('source');
const imgPath = join(dirname(tmxFile), imageSource!);
if (existsSync(imgPath)) {
imageLayerTextures.push(imgPath);
} else {
console.warn('Parse %s failed.', imgPath);
}
}
}
return imgFullPaths.concat(imageLayerTextures);
}
/**
* 读取文件路径下 image 的 source 路径信息以及对应的文件名
* @param tsxDoc
* @param tsxPath
* @returns imageFullPath
*/
export async function parseTilesetImages(tsxDoc: Element | Document, tsxPath: string) {
const images = tsxDoc.getElementsByTagName('image');
const imageFullPath: string[] = [];
for (let i = 0; i < images.length; i++) {
const image = images[i];
const imageCfg = image.getAttribute('source');
if (imageCfg) {
const imgPath = join(dirname(tsxPath), imageCfg);
imageFullPath.push(imgPath);
}
}
return imageFullPath;
}
export function getColor(node: any) {
if (node && node._color) {
return {
"__type__": "cc.Color",
"r": node._color.r,
"g": node._color.g,
"b": node._color.b,
"a": node._color.a,
};
}
}
const halfToRad = 0.5 * Math.PI / 180.0;
export function fromEuler(out: any, x: number, y: number, z: number) {
x *= halfToRad;
y *= halfToRad;
z *= halfToRad;
const sx = Math.sin(x);
const cx = Math.cos(x);
const sy = Math.sin(y);
const cy = Math.cos(y);
const sz = Math.sin(z);
const cz = Math.cos(z);
out.x = sx * cy * cz + cx * sy * sz;
out.y = cx * sy * cz + sx * cy * sz;
out.z = cx * cy * sz - sx * sy * cz;
out.w = cx * cy * cz - sx * sy * sz;
return out;
}
/**
* 项目内的脚本文件名称不能重复
*/
export const scriptName = {
allScripts: null,
allClassNames: [],
timer: 0,
fileName: '',
className: '',
async isValid(fileName: string) {
const className = this.getValidClassName(fileName);
if (!className) {
return { state: 'errorScriptClassName' };
}
const exist = await Editor.Message.request('scene', 'query-component-has-script', className);
if (!exist) {
return { state: '' };
}
return { state: 'errorScriptClassNameExist', message: className };
},
async getValidFileName(fileName: string) {
fileName = fileName.trim().replace(/[^a-zA-Z0-9_-]/g, '');
const baseName = fileName;
let index = 0;
while ((await this.isValid(fileName)).state) {
index++;
const padString = `-${index.toString().padStart(3, '0')}`;
fileName = `${baseName}${padString}`;
}
return fileName;
},
getValidClassName(fileName: string) {
/**
* 类名转为大驼峰格式:
* 头部不能有数字
* 不含特殊字符
* 符号和空格作为间隔,每个间隔后的首字母大写,如:
* 0my class_name-for#demo! 转后为 MyClassNameForDemo
*/
fileName = fileName.trim().replace(/^[^a-zA-Z]+/g, '');
const parts = fileName.match(/[a-zA-Z0-9]+/g);
if (parts) {
return parts
.filter(Boolean)
.map((part) => part[0].toLocaleUpperCase() + part.substr(1))
.join('');
}
return '';
},
};
// 排序
export function sizeSorting(a: any, b: any) {
const aID = a.__id__;
const bID = b.__id__;
return bID - aID;
}
// 比对版本号
export function compareVersion(versionA: string, versionB: string) {
const a = versionA.split('.');
const b = versionB.split('.');
const length = Math.max(a.length, b.length);
for (let i = 0; i < length; i++) {
const an = a[i] || 0;
const bn = b[i] || 0;
if (Number(an) < Number(bn)) {
return -1;
}
if (Number(an) > Number(bn)) {
return 1;
}
}
return 0;
}
export function getComponentByType(nodeID: number, componentType: string, json: any) {
const node = json[nodeID];
const components = node._components.map((component: any) => json[component.__id__]);
return components.find((component: any) => component.__type__ === componentType);
}
export async function getDesignResolution() {
const width = await Editor.Profile.getProject('project', 'general.designResolution.width');
const height = await Editor.Profile.getProject('project', 'general.designResolution.height');
return {
width: width || 960,
height: height || 640,
}
}
const _extendIndex = [
1, 2, 3, 4, 5,
7, 8, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24,
26, 27, 28, 29, 30
];
export function nameToId(name: string, extend?: number) {
if (!extend) {
extend = 0;
}
const md5 = createHash('md5').update(name).digest('hex');
let id = md5[0] + md5[6] + md5[16] + md5[25] + md5[31];
for (let i = 0; i < extend; i++) {
id += md5[_extendIndex[i]];
}
return id;
}