const path = require("path") /** 包名 */ const PACKAGE_NAME = require('../package.json').name; const scriptTemplate = require('../package.json').scriptTemplate; const StringUtil = require("./../src/utils/string-util") const fs = require("fs"); let wait = ms => new Promise(async resolve => setTimeout(resolve, ms)); let old = cc._decorator.ccclass; const TOOL_CACHE_KEY = '__TOOL_CACHE_KEY__'; const CACHE_KEY = '__ccclassCache__'; if (!cc._decorator.ccclass["isnew"]) { cc._decorator.ccclass = function (ctor, name) { let s = ctor[CACHE_KEY]; ctor[TOOL_CACHE_KEY] = ctor[CACHE_KEY]; let wu = ctor[TOOL_CACHE_KEY]; let ret = old.call(this, ctor, name); return ret; }; } cc._decorator.ccclass["isnew"] = true; let HeadMenu = { "node": "cc.Node", "list": "cc.ScrollView", "lb": "cc.Label", "spr": "cc.Sprite", "rich": "cc.RichText", "btn": "cc.Button", "bar": "cc.ProgressBar", "edit": "cc.EditBox", "video": "cc.VideoPlayer", "bone": "dragonBones.ArmatureDisplay", "spine": "sp.Skeleton" }; let replace = '\n\ /*自动生成*/\n\ @property({type:{type}, displayName:""})\n\ private {name}:{type} = null;\n\ '; const PattStart = '===========================自动绑定组件开始==========================='; const PattEnd = '===========================自动绑定组件结束==========================='; const PattStart_BTN = '===========================自动生成按钮事件开始=========================='; const PattEnd_BTN = '===========================自动生成按钮事件结束=========================='; //缓存全部需要绑定的节点 let caches = {}; let oldCodeMap = {} let oldCodeFunction = {} module.exports = { // 新建ts模板路径 getTsTemplatePath(){ return Editor.url(`packages://${PACKAGE_NAME}/${scriptTemplate}`, 'utf8') }, print:function(nodes){ for (const key in nodes) { const node = nodes[key]; Editor.log(key,node); } }, /** * 等待3次检查 要检查绑定了才刷新 */ async bindRef(data){ await wait(1000); this.bindSciprt(data); await wait(1000); this.bindSciprt(data); //刷新脚本 Editor.assetdb.refresh(data.jsPath,(err, results)=>{ if(err){ data.event.reply(null,"刷新失败"+err); }else{ data.event.reply("刷新成功",results); } }); Editor.Scene.callSceneScript(PACKAGE_NAME, 'add-script',function(error,obj){ if(obj){ return Editor.log(obj); } //执行回来的操作 }); }, /** * 绑定脚本到Node上 */ bindSciprt(data){ let node = this.findNode(data.uuid) let comp = node.getComponent(data.jsFileName); if(!comp){ try { comp = cc.require(data.jsFileName) } catch(t) {} // 添加组件 if (comp){ node.addComponent(data.jsFileName); } } }, async createTs(node,event){ let self = this; let uuid = node.uuid; let file_type = ".ts"; let jsFileName = node.name; jsFileName = StringUtil.firstLowerCase(jsFileName); let templateTs = fs.readFileSync(this.getTsTemplatePath(),'utf-8'); //获取当前节点的路径 let obj = await this.getCurrSceneUrl(); let url = this.getNewFileSavePath(obj.url); //创建文件夹 this.createDir(Editor.url(url)); templateTs = templateTs.replace(/ScriptTemplate/g, jsFileName); let jsPath = `${url}${jsFileName}${file_type}`; //Editor.log("创建脚本位置",jsPath) Editor.assetdb.createOrSave( jsPath, templateTs, ( err, results )=> { if(err) return event.reply(null,"创建脚本失败"); self.bindRef({ uuid:uuid, node:node, jsFileName:jsFileName, jsPath:jsPath, event:event }); }); }, /** * 添加脚本 * @param {*} event * @param {{ uuids: string[], name: string }} data 数据 */ 'add-script':async function(event) { oldCodeMap = {} let obj = await this.getCurrSceneUrl(); if(!obj) return event.reply("当前没有选中任何节点"); let self = this; // 发消息给主进程进行关键词匹配 let file_type = localStorage.getItem("newFileType")|| ".ts" //当前选中的Node let head_node = Editor.Selection.curSelection('node')[0]; //当前选中Node之下的全部节点 let nodes = this.findNode(head_node); //只挂载脚本 let jsFile = this.isHasJsFile(nodes); if (jsFile) { let node_uids = Editor.Selection.curSelection("node"); //Editor.log(node_uids, node_uids.length); for (let i = 0; i < node_uids.length; i++) { let id = node_uids[i]; Editor.Scene.callSceneScript(PACKAGE_NAME, "auto_code_binder", id); } }else { //创建挂载脚本 this.createTs(nodes,event); } }, /** * 生成代码属性 * * 属性 写入文件 */ "auto_code_binder":function(event,uuid){ caches = {}; let node = cc.engine.getInstanceById(uuid); if (!node) { Editor.log("没有找到需要的节点"); return; } //开始整个文件查找节点 this.findChildren(node); //获取组件脚本绑定 let coms = node.getComponents(cc.Component); if(coms && coms.length <= 0){ Editor.warn("该节点下没有脚本组件"); return; } for (let com of coms) { let name = cc.js.getClassName(com); let btns = {}; //排除系统组件 if (!name.startsWith("cc.") && !name.startsWith("sp.")) { // Editor.warn("自定义脚本",name); let comUuid = com.__scriptUuid; let path = Editor.remote.assetdb.uuidToFspath(comUuid); let tsScriptData = fs.readFileSync(path, "utf-8"); let patt = new RegExp(`${PattStart}[\\s\\S]*${PattEnd}\*`, "gm"); let res = tsScriptData.match(patt); let data = res === null || res === void 0 ? void 0 : res.toString(); // Editor.warn("代码内容",data); //结束 if (!res) { Editor.log("=====脚本中没有发现替代注释 请在需要添加组件的地方复制粘贴以下注释======"); Editor.log(`/*${PattStart}*/`); Editor.log(`/*${PattEnd}*/`); return; } this.parseText(data); let str = `${PattStart}*/`; let seq = 0; let funcSeq = 0; for (const key in caches) { const node = caches[key]; if(node){ let n = this.getHeadString(key); let type = HeadMenu[n]; if(type == "cc.Button"){ btns[key] = key; } if(!type){ Editor.warn("发现不知道的类型",key); continue; } let old = oldCodeMap[key]; if (old) { if(seq == 0){str += "\n"} str += "\t" + old.content + "\n"; seq +=1; }else{ let txmp = replace; if (type) { txmp = txmp.replace(new RegExp(/({type})/g,"gm"),type) txmp = txmp.replace("{name}", key); } str += txmp; } } } str += "\n\t/*" + PattEnd + ""; tsScriptData = tsScriptData.replace(res.toString(), str); //生成按钮方法属性 let patt_btn = new RegExp(`${PattStart_BTN}[\\s\\S]*${PattEnd_BTN}\*`, "gm"); let result = tsScriptData.match(patt_btn); let btnFuncs = result === null || result === void 0 ? void 0 : result.toString(); //结束 if (!btnFuncs) { //Editor.log("=====脚本中没有发现替代注释 请在需要添加组件的地方复制粘贴以下注释======"); //Editor.log(`/*${PattStart}*/`); //Editor.log(`/*${PattEnd}*/`); return; } oldCodeFunction = {}; this.parseFunction(btnFuncs); let newStr = `${PattStart_BTN}*/\n`; for (const key in btns) { const name = btns[key]; let funcName = `on${this.getFunctionString(name)}TouchEnd`; let old = oldCodeFunction[funcName]; if (old) { if(funcSeq == 0){newStr += "\n"} newStr += "\t" + old + "\n"; funcSeq +=1; }else{ let str = `\t${funcName}(){}\n\n` newStr += str ; } } newStr += "\n\t/*" + PattEnd_BTN; tsScriptData = tsScriptData.replace(result.toString(), newStr); fs.writeFileSync(path, tsScriptData); this.waitRefreshScript(comUuid,node); } } }, parseFunction(data){ if (!data) { return; } let reg = data.match(/on*([\s\S]*?)}/g); if (!reg) { return; } for (let i = 0; i < (reg === null || reg === void 0 ? void 0 : reg.length); i++) { let str = reg[i]; let type = str.match(/on([\s\S]*?)TouchEnd/)[0]; if (!type) { continue; } oldCodeFunction[type] = str; } }, parseText(data){ if (!data) { return; } let reg = data.match(/\/\*自动生成\*\/([\s\S]*?);/g); if (!reg) { //Editor.log("没有匹配"); return; } for (let i = 0; i < (reg === null || reg === void 0 ? void 0 : reg.length); i++) { let str = reg[i]; let type = str.match(/type:([\s\S]*?),/)[0]; if (!type) { continue; } type = type === null || type === void 0 ? void 0 : type.replace("type:", ""); type = type === null || type === void 0 ? void 0 : type.replace(",", ""); type = type.trim(); let name = str.match(/(private|public)([\s\S]*?):/)[0]; if (!name) { continue; } name = name === null || name === void 0 ? void 0 : name.replace(/(private|public)/, ""); name = name === null || name === void 0 ? void 0 : name.replace(":", ""); name = name.trim(); let old = {}; old.content = str; old.name = name; old.type = type; oldCodeMap[name] = old; } }, //执行刷新 async waitRefreshScript(comUuid,node){ let obj = await this.getCurrSceneUrl(); let assetPath = Editor.remote.assetdb.uuidToUrl(comUuid); Editor.assetdb.refresh(assetPath); Editor.assetdb.refresh(obj.url); //wait(2000) //Editor.Scene.callSceneScript(PACKAGE_NAME, "bind_Code_ByNode"); }, //生成属性到脚本 "generate_attribute":function(){ let head_node = Editor.Selection.curSelection('node')[0]; if(!head_node) return Editor.log("没有选中节点"); //当前选中Node之下的全部节点 let nodes = this.findNode(head_node); //只挂载脚本 let jsFile = this.isHasJsFile(nodes); if (jsFile) { //Editor.log("当前有脚本",jsFile.__classname__) let node_uids = Editor.Selection.curSelection("node"); Editor.log(node_uids, node_uids.length); for (let i = 0; i < node_uids.length; i++) { let id = node_uids[i]; Editor.Scene.callSceneScript(PACKAGE_NAME, "auto_code_binder", id); } }else { //创建挂载脚本 Editor.log("当前节点没有挂载节点") } }, /** * 绑定节点到代码 */ "bind_Code_ByNode":async function(event){ let uuid = Editor.Selection.curSelection('node'); let node = cc.engine.getInstanceById(uuid); caches = {}; if (!node) { Editor.log("没有找到需要的节点"); return; } //开始整个文件查找节点 this.findChildren(node); this.bindCodeByNode(); }, //开始关联 bindCodeByNode(){ let uuid = Editor.Selection.curSelection('node'); let node = cc.engine.getInstanceById(uuid); if(!node)return Editor.warn("====请选择一个节========"); ; //记录绑定信息 let coms = node.getComponents(cc.Component); for (let com of coms) { let name = cc.js.getClassName(com); //过滤 if (name.startsWith("cc.") || name.startsWith("sp.")) { continue }; let comUuid = com.__scriptUuid; let cache = com.constructor[TOOL_CACHE_KEY]; if(!cache){ return; } for (let key in com) { if (caches[key]) { if(key == "constructor") continue; let data = cache.proto.properties[key]; let children = caches[key]; if (children) { let nodes = children.filter(node => { if (data.type == cc.Node) { return node; } return node.getComponent(data.type); }); if (nodes && nodes.length) { let first = nodes[0]; let needCom = null; if (data.type == cc.Node) { needCom = first; } else { needCom = first.getComponent(data.type); } com[key] = needCom; //按钮统一格式 if(cc.Button == data.type){ let clickEvents = needCom.clickEvents; //查找是否已经绑定过了 let funcName = `on${this.getFunctionString(key)}TouchEnd` let isBind = false; //查找是否绑定了规定函数 for (let index = 0; index < clickEvents.length; index++) { const clickEvent = clickEvents[index]; if(clickEvent.handler == funcName){ isBind = true; } } //绑定自动生成的函数 if(!isBind){ var clickEventHandler = new cc.Component.EventHandler(); //这个 node 节点是你的事件处理代码组件所属的节点 clickEventHandler.target = node; clickEventHandler.component = name;//这个是代码文件名 clickEventHandler.handler = funcName; //回调函数 needCom.clickEvents.push(clickEventHandler); } //设置默认的按钮样式 // needCom needCom.transition = cc.Button.Transition.SCALE; needCom.duration = 0.1; needCom.zoomScale = 1.2; } continue; } } } } let assetPath = Editor.remote.assetdb.uuidToUrl(comUuid); Editor.assetdb.refresh(assetPath); //Editor.Ipc.sendToMain('assets:open-text-file', comUuid); } Editor.log("=======绑定结束========"); }, getHeadString:function(str, fortmat = "_") { if (!str) { return str; } let res = str.split(fortmat); return res[1]; }, getFunctionString:function(str, fortmat = "_") { if (!str) { return str; } let res = str.split(fortmat); let name = res[0] if(name.indexOf("$") > -1){ name = name.substring(name.indexOf("$")+1,name.length); return StringUtil.firstUpperCase(name); } return StringUtil.firstUpperCase(name); }, findChildren:function(node) { let children = node.children; for (let i = 0; i < children.length; i++) { let child = children[i]; if(child.name.indexOf("@") > -1) continue; let idx = child.name.indexOf("$"); if(idx > -1){ if(caches[child.name] == null){ caches[child.name] = []; } caches[child.name].push(child); } if(child.childrenCount > 0){ this.findChildren(child) } } }, dump(obj){ let str = "" let i = 0 for(let name in obj) { try{ str += name + ": " + obj[name] + "\n" }catch(err){ str += name + ": " + typeof obj[name] + "\n" } i ++; if (i>100){ str+="...more" break; } } Editor.log("dump:"+obj,str) }, // 保存新建脚本路径 getNewFileSavePath(node_url){ // 如果是预制体 脚本就生成到Sciprt下 if(node_url.lastIndexOf(".prefab") > -1){ let str = node_url.substr(0,node_url.lastIndexOf("/")) return str.replace("prefab","Script") + "/"; }else{ return node_url.substr(0,node_url.lastIndexOf("/")) + '/'; //保存到预制节点的同级目录 } // return node_url.substr(0,node_url.lastIndexOf("/")) + '/'; }, // 创建目录,绝对路径 createDir(dirPath){ if ( fs.existsSync(dirPath) ) return; let paths = dirPath.split(path.sep);//分割路径 let path_ = ""; for (let n = 0; n < paths.length; n++) { path_ += paths[n] + path.sep; if(!fs.existsSync(path_)){ fs.mkdirSync(path_); } } }, // 获得当前打开的场景文件路径 async getCurrSceneUrl(callFunc){ return new Promise((resolve)=>{ let obj = {}; let scene = cc.director.getScene(); if(scene){ let url = Editor.remote.assetdb.uuidToUrl(scene.uuid); if (url){ obj.url = url obj.isScene = true obj.uuid = scene.uuid; return resolve(obj); }else{ // 当前打开的预制节点路径 Editor.Ipc.sendToMain(`${PACKAGE_NAME}:getPrefabUuid`,{}, function (error, answer) { if (answer != null){ obj.url = Editor.remote.assetdb.uuidToUrl(answer) obj.isScene = false obj.uuid = answer; return resolve(obj); }else{ resolve(null); } }); } }else{ // 当前打开的预制节点路径 Editor.Ipc.sendToMain(`${PACKAGE_NAME}:getPrefabUuid`,{}, function (error, answer) { if (answer != null){ obj.url = Editor.remote.assetdb.uuidToUrl(answer) obj.isScene = false obj.uuid = answer; return resolve(obj); }else{ resolve(null); } }); } }) }, // 检测场景是否存在该子节点并返回相关信息 findNode(select_uuid) { var canvas = cc.director.getScene(); var ret_node if (canvas && select_uuid) { this.getNodeChildren(canvas,(node)=>{ if (node.uuid == select_uuid){ ret_node = node; return ret_node; } }) } return ret_node; }, // 遍历所有深层子节点 getNodeChildren(node,callFunc) { if (!node) return; let nodes = node.getChildren(); nodes.forEach((v)=>{ v._path_str = (node._path_str || node.name)+"/" + v.name; this.getNodeChildren(v,callFunc) }); callFunc(node) }, getNodeReChildren(node,callFunc) { if (!node) return; let nodes = node.getChildren(); callFunc(node) nodes.forEach((v)=>{ v._path_str = (node._path_str || node.name)+"/" + v.name; this.getNodeReChildren(v,callFunc) }); }, isHasJsFile(node){ if(!node) {return false}; return this.getJsFileList(node)[0]; }, getJsFileList(node){ if(!node) {return []}; let list = []; node.getComponents(cc.Component).forEach((v)=>{ if(v.__classname__ && v.__classname__.indexOf(".") == -1) list.push(v); //js脚本 }); return list; }, }