'use strict'; // Anim Studio 导入器 —— Cocos Creator 3.8.x 编辑器扩展 // 菜单:扩展 → Anim Studio → 1.导入 cocos-pack 资源 / 2.布置演示节点 const fs = require('fs'); const path = require('path'); function projectAssets() { return path.join(Editor.Project.path, 'assets'); } // 递归拷贝(Node 16+ 自带 fs.cpSync;做个兜底) function copyDir(src, dst) { if (fs.cpSync) { fs.cpSync(src, dst, { recursive: true }); return; } fs.mkdirSync(dst, { recursive: true }); for (const name of fs.readdirSync(src)) { const s = path.join(src, name), d = path.join(dst, name); if (fs.statSync(s).isDirectory()) copyDir(s, d); else fs.copyFileSync(s, d); } } exports.methods = { // ---------- 1. 导入资源 ---------- async importPack() { let picked; try { picked = await Editor.Dialog.select({ title: '选择 Anim Studio 导出的 cocos-pack 文件夹', path: Editor.Project.path, type: 'directory', }); } catch (e) { return Editor.Dialog.error('Anim Studio', { detail: '打开文件选择框失败:' + e }); } if (!picked || picked.canceled || !picked.filePaths || !picked.filePaths.length) return; const pack = picked.filePaths[0]; // 兼容:用户既可能选 cocos-pack 本身,也可能选到里面的 assets let srcAssets = path.join(pack, 'assets'); if (!fs.existsSync(srcAssets) && fs.existsSync(path.join(pack, 'resources'))) { srcAssets = pack; // 选到了 assets 目录 } if (!fs.existsSync(srcAssets)) { return Editor.Dialog.error('Anim Studio', { detail: '没找到 assets/(或 resources/)。\n你要选的是 Anim Studio 导出的「cocos-pack」文件夹(里面有 assets/resources 和 assets/scripts),不是你的 Cocos 项目文件夹。', }); } const dstAssets = projectAssets(); // 防呆:选到了项目自己(src 与 dest 同根),会导致“自己拷给自己” if (path.resolve(srcAssets) === path.resolve(dstAssets) || path.resolve(srcAssets).startsWith(path.resolve(dstAssets) + path.sep)) { return Editor.Dialog.error('Anim Studio', { detail: '你选到了当前项目自己的 assets 了。\n请改选 Anim Studio 网站「导出」生成的 cocos-pack 文件夹(通常在 out//cocos-pack/),它里面才有要导入的素材。', }); } const copied = []; try { for (const sub of ['resources', 'scripts']) { const s = path.join(srcAssets, sub); if (!fs.existsSync(s)) continue; const d = path.join(dstAssets, sub); // 再保险:逐个子目录也跳过 src===dest if (path.resolve(s) === path.resolve(d)) continue; copyDir(s, d); copied.push(sub); } } catch (e) { return Editor.Dialog.error('Anim Studio', { detail: '拷贝文件失败:' + e }); } if (!copied.length) { return Editor.Dialog.error('Anim Studio', { detail: 'assets 里没有 resources / scripts 子目录。' }); } // 让编辑器重新导入资源数据库 try { await Editor.Message.request('asset-db', 'refresh-asset', 'db://assets'); } catch (e) {} Editor.Dialog.info('Anim Studio', { detail: '已导入:' + copied.join('、') + '。\n等右下角导入进度条走完后,新建或打开一个场景,再点菜单「2. 在当前场景布置演示节点」。', buttons: ['好的'], }); }, // ---------- 2. 布置演示节点 ---------- async buildScene() { // 先确认有打开的场景 let scene; try { scene = await Editor.Message.request('scene', 'query-is-ready'); } catch (e) {} try { // 找一个 Canvas 作父节点(新建 2D 场景默认就有,名为 Canvas) let parent = null; try { const tree = await Editor.Message.request('scene', 'query-node-tree'); parent = findCanvasUuid(tree); } catch (e) {} const opt = { name: 'AnimStudioDemo' }; if (parent) opt.parent = parent; const nodeUuid = await Editor.Message.request('scene', 'create-node', opt); const uuid = typeof nodeUuid === 'string' ? nodeUuid : (nodeUuid && nodeUuid.uuid); await Editor.Message.request('scene', 'create-component', { uuid, component: 'JellyDemo' }); Editor.Dialog.info('Anim Studio', { detail: '已创建节点「AnimStudioDemo」并挂上 JellyDemo 组件。\n' + '按 Ctrl/Cmd+S 保存场景,再点上方 ▶ 播放即可。\n' + (parent ? '' : '(没检测到 Canvas,如果画面空白,请确认场景里有 Canvas,并把该节点拖到 Canvas 下。)'), buttons: ['完成'], }); } catch (e) { Editor.Dialog.error('Anim Studio', { detail: '自动布置失败(多半是没有打开场景,或脚本还没导入完):' + e + '\n\n手动兜底:新建/打开一个场景 → 在 Canvas 下右键新建空节点 → 把 assets/scripts/JellyDemo.ts 拖到它的属性检查器上 → 保存并播放。', }); } }, }; // 在节点树里找第一个名为 Canvas 的节点 uuid function findCanvasUuid(node) { if (!node) return null; if (Array.isArray(node)) { for (const n of node) { const r = findCanvasUuid(n); if (r) return r; } return null; } if (node.name === 'Canvas' && node.uuid) return node.uuid; if (node.children) return findCanvasUuid(node.children); return null; } exports.load = function () {}; exports.unload = function () {};