import { Button, director, EditBox, Label, Node, path, RichText, ScrollView, Sorting2D, sp, Sprite, UIRenderer } from 'cc'; import { join } from 'path'; const Fs = require('fs'); const Path = require('path'); // 临时在当前模块增加编辑器内的模块为搜索路径,为了能够正常 require 到 cc 模块,后续版本将优化调用方式 module.paths.push(join(Editor.App.path, 'node_modules')); // 当前版本需要在 module.paths 修改后才能正常使用 cc 模块 // 并且如果希望正常显示 cc 的定义,需要手动将 engine 文件夹里的 cc.d.ts 添加到插件的 tsconfig 里 // 当前版本的 cc 定义文件可以在当前项目的 temp/declarations/cc.d.ts 找到 // 获取项目路径 const projectPath = Editor.Project.path; // 定义输出文件路径 const outputPath = path.join(projectPath, 'assets', 'res', 'font', 'str.txt'); /** * @en * @zh 为扩展的主进程的注册方法 */ export const methods: { [key: string]: (...any: any) => any } = { log() { }, async printSpineAnim() { let s = director.getScene(); let cur = Editor.Selection.getLastSelected("node"); let node = this.getNodeByuuid(s, cur) as Node; let spAnim = node.getComponent(sp.Skeleton); if (!spAnim) return; console.log("选中节点包含以下动画:"); //打印所有动画名 let anims = spAnim?.skeletonData?.getRuntimeData()?.animations; anims?.forEach(x => { console.log("动画名:" + x.name); }); console.log("选中节点当前动画已复制成功:" + spAnim?.animation); //复制动画名称 Editor.Clipboard.write('text', spAnim?.animation); }, async changeNodeName() { let s = director.getScene(); let cur = Editor.Selection.getLastSelected("node"); let node = this.getNodeByuuid(s, cur) as Node; if (!node) return; console.log("选中节点:" + node.name); let name = Editor.Clipboard.read('text'); console.log("剪切板内容:" + name); //设置node的名字为剪切板内容 node.name = name + ""; }, async uiScript() { let s = director.getScene(); let cur = Editor.Selection.getLastSelected("node"); console.log("选中节点:" + cur); let node = this.getNodeByuuid(s, cur) as Node; let autoName = "Auto_" + node.name; let nodes = this.getAllNodes(node, "node"); let funcNames = []; //console.log("得到的所有节点", nodes); let str = ""; str += ` //********************* // create by 流云 // 插件自动生成的节点脚本 // 请勿修改(自动生成会覆盖) // 请通过子类继承使用 // 如何生成可直接调用的UI节点和组件? // 1.在场景节点树中选中要生成的UI根节点 // 2.点击菜单"tool"->“生成UI脚本” 或者使用快捷键(快捷键:Ctrl+Shift+C) // 3.点击后会在script/ui/目录中生成一个以选中节点命名的文件夹,文件夹中会生成两个脚本文件,“Auto_”开头的就是本文件,不可修改,另一个是“UI”+选中节点名称命名的脚本文件,可修改,是需要挂载到选中节点的脚本文件,给用户使用的写逻辑的脚本,只会生成一次 // 4.找到第3步描述的“UI”+选中节点名称命名的脚本文件,将其挂载到选中节点上(第一生成需要挂载,后续修改场景,重复生成的时候,该脚本不会再生成,不用担心会覆盖逻辑脚本) // 下面是类型示例 // 1.按钮,节点名以“btn_”或者“Btn_”开头或者节点添加了Button组件, 例:btn_Close,会生成btn_Close节点和click_btn_Close()方法 // 2.标签,节点名以“lb_”开头, 例:lb_title,会生成lb_title的Label组件 // 3.输入框,节点名以“eb_”开头, 例:eb_input,会生成eb_input的EditBox组件 // 4.滚动视图,节点名以“sv_”开头, 例:sv_list,会生成sv_list的ScrollView组件 // 5.图片,节点名以“img_”或者sp_“”开头, 例:img_Icon,会生成img_Icon的Sprite组件 // 6.普通节点,节点名以“ui_”或者“Ui_”或者“UI_”开头, 例:ui_Content,会生成ui_Content的Node节点 //********************* import { _decorator, Component, Node, find, Sprite, Label, Button, ScrollView, EditBox, RichText } from 'cc'; import { UIBase } from '../../../mx/module/ui/UIBase'; const { ccclass } = _decorator; @ccclass('` + autoName + `') export class ` + autoName + ` extends UIBase {\r\n`; console.log("得到的所有节点", JSON.stringify(nodes)); for (let i = 0; i < nodes.length; i++) { let tName = nodes[i].nodeName; if (nodes.filter((x: any) => x.nodeName == tName).length > 1) { tName = nodes[i].path.replace(/\//g, "_"); } let type = nodes[i].type != "" && nodes[i].type != "Button" ? nodes[i].type : "Node"; str += " " + tName + `:${type};\r\n`; } str += ` onLoad(){ super.onLoad();\r\n`; for (let i = 0; i < nodes.length; i++) { let tName = nodes[i].nodeName; if (nodes.filter((x: any) => x.nodeName == tName).length > 1) { tName = nodes[i].path.replace(/\//g, "_"); } if (nodes[i].type == "Button") { funcNames.push(tName); } let lastStr = ""; if (nodes[i].type == "ScrollView") { lastStr = "?.getComponent(ScrollView)"; } if (nodes[i].type == "Label") { lastStr = "?.getComponent(Label)"; } if (nodes[i].type == "EditBox") { lastStr = "?.getComponent(EditBox)"; } if (nodes[i].type == "Sprite") { lastStr = "?.getComponent(Sprite)"; } if (nodes[i].type == "Button") { lastStr = ""; } if (nodes[i].type == "RichText") { lastStr = "?.getComponent(RichText)"; } str += ` this.${tName} = find("${nodes[i].path}",this.node)${lastStr};\r\n`; } str += ` }\r\n`; str += ` onEnable() {\r\n`; for (let i = 0; i < funcNames.length; i++) { str += ` this.${funcNames[i]}.on(Node.EventType.TOUCH_END, this.click_${funcNames[i]}, this);\r\n`; } str += ` }\r\n`; str += ` onDisable() {\r\n`; for (let i = 0; i < funcNames.length; i++) { str += ` this.${funcNames[i]}.off(Node.EventType.TOUCH_END, this.click_${funcNames[i]}, this);\r\n`; } str += ` }\r\n`; for (let i = 0; i < funcNames.length; i++) { str += ` click_${funcNames[i]}() {}\r\n`; } str += `}`; Editor.Message.send( "asset-db", "create-asset", `db://assets/script/ui/${node.name}/${autoName}.ts`, str, { overwrite: true } ); let className = "UI" + node.name; let path = `db://assets/script/ui/${node.name}/${className}.ts`; let r = await Editor.Message.request( "asset-db", "query-asset-info", path ); console.log("生成成功?", r); this.extractUsedChars(); if (r) return; let comp = node.getComponent(className); console.log("组件:", comp); if (!comp) { str = ` //********************* // create by 流云 // time: ${new Date().toDateString()} // desc: //********************* import { _decorator, Component, Node , find} from 'cc'; import { ${autoName} } from './${autoName}'; const { ccclass, property } = _decorator; @ccclass('${className}') export class ${className} extends ${autoName} { onLoad(): void { super.onLoad(); } onEnable(): void { super.onEnable(); } onDisable(): void { super.onDisable(); } onInit(): void { } } `; Editor.Message.send( "asset-db", "create-asset", path, str ); } //node.addComponent(className); }, getNodeByuuid(root: Node, uuid) { let node = null; for (let i = 0; i < root.children.length; i++) { let c = root.children[i]; if (c.uuid == uuid) { node = c; break; } } if (node != null) return node; else { for (let i = 0; i < root.children.length; i++) { let c = root.children[i]; let n = this.getNodeByuuid(c, uuid); if (n) return n; } } }, getAllNodes(root, parentName, path = "") { let tempArr: Array<{}> = []; for (let i = 0; i < root.children.length; i++) { let c: Node = root.children[i]; let p = path == "" ? c.name : path + "/" + c.name; let typ = ""; // let sortComp = c.getComponent(Sorting2D); // if (sortComp) { // sortComp.destroy(); // } if (c.getComponent(Label) != null && c.name.indexOf("lb_") >= 0) { typ = "Label"; } if (c.getComponent(RichText) != null && c.name.indexOf("rt_") >= 0) { typ = "RichText"; } if (c.getComponent(EditBox) != null && c.name.indexOf("eb_") >= 0) { typ = "EditBox"; } if (c.getComponent(Sprite) != null && (c.name.indexOf("img_") >= 0 || c.name.indexOf("sp_") >= 0)) { typ = "Sprite"; } if (c.getComponent(ScrollView) != null && c.name.indexOf("sv_") >= 0) { typ = "ScrollView"; } if (c.name.indexOf("ui_") >= 0 || c.name.indexOf("UI_") >= 0 || c.name.indexOf("Ui_") >= 0) { typ = "Node"; } if (c.getComponent(Button) != null || c.name.indexOf("btn_") >= 0 || c.name.indexOf("Btn_") >= 0) { typ = "Button"; } if (!/[\u4e00-\u9fff]/.test(c.name) && !/^\d/.test(c.name) && !/-/.test(c.name) && !/\s/.test(c.name) && (typ == "Button" || typ == "Label" || typ == "Sprite" || typ == "ScrollView" || typ == "Node" || typ == "EditBox" || typ == "RichText")) tempArr.push({ nodeName: c.name, parentName: parentName, path: p, type: typ }); if (c.children.length > 0) { let arr = this.getAllNodes(c, c.name, p); tempArr = tempArr.concat(arr); } } return tempArr; }, /** * 获取节点树 * @param {string} filePath 文件路径 * @returns {object} */ getNodeTree(filePath) { // 获取缓存 let cache = this.cache as any; if (!cache) { cache = this.cache = Object.create(null); } // 从缓存中读取 if (!cache[filePath]) { // 将资源数据转为节点树 const data = JSON.parse(Fs.readFileSync(filePath)); cache[filePath] = this.convertToNodeTree(data); } }, // 提取所有使用的字符 extractUsedChars() { const usedChars = new Set(); // 遍历项目中的 assets 目录 const assetsPath = path.join(projectPath, 'assets'); if (Fs.existsSync(assetsPath)) { this.traverseDirectory(assetsPath, usedChars); } // 将字符集合转换为字符串 const charsString = Array.from(usedChars).join(''); // 确保输出目录存在 const outputDir = path.dirname(outputPath); if (!Fs.existsSync(outputDir)) { Fs.mkdirSync(outputDir, { recursive: true }); } // 写入文件 Fs.writeFileSync(outputPath, charsString); console.log(`Extracted ${usedChars.size} unique characters to ${outputPath}`); }, // 遍历目录并提取字符 traverseDirectory(dirPath, usedChars) { const files = Fs.readdirSync(dirPath); files.forEach((file: any) => { const filePath = path.join(dirPath, file); const stat = Fs.statSync(filePath); if (stat.isDirectory()) { // 如果是目录,递归遍历 this.traverseDirectory(filePath, usedChars); } else if (stat.isFile()) { // 如果是文件,读取内容并提取字符 const ext = path.extname(file).toLowerCase(); if (ext === '.js' || ext === '.ts' || ext === '.json' || ext === '.fire' || ext === '.prefab' || ext === '.csv' || ext === '.txt') { try { const content = Fs.readFileSync(filePath, 'utf-8'); for (let char of content) { usedChars.add(char); } } catch (err) { console.warn(`Failed to read file: ${filePath}, error: ${err}`); } } } }); } }; /** * @en Hooks triggered after extension loading is complete * @zh 扩展加载完成后触发的钩子 */ export const load = function () { let id = Editor.Selection.getLastSelected("Node"); console.log("选中节点:" + id); }; export const update = function () { let id = Editor.Selection.getLastSelected("Node"); console.log("选中节点:" + id); } /** * @en Hooks triggered after extension uninstallation is complete * @zh 扩展卸载完成后触发的钩子 */ export const unload = function () { };