scene.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. const path = require("path")
  2. /** 包名 */
  3. const PACKAGE_NAME = require('../package.json').name;
  4. const scriptTemplate = require('../package.json').scriptTemplate;
  5. const StringUtil = require("./../src/utils/string-util")
  6. const fs = require("fs");
  7. let wait = ms => new Promise(async resolve => setTimeout(resolve, ms));
  8. let old = cc._decorator.ccclass;
  9. const TOOL_CACHE_KEY = '__TOOL_CACHE_KEY__';
  10. const CACHE_KEY = '__ccclassCache__';
  11. if (!cc._decorator.ccclass["isnew"]) {
  12. cc._decorator.ccclass = function (ctor, name) {
  13. let s = ctor[CACHE_KEY];
  14. ctor[TOOL_CACHE_KEY] = ctor[CACHE_KEY];
  15. let wu = ctor[TOOL_CACHE_KEY];
  16. let ret = old.call(this, ctor, name);
  17. return ret;
  18. };
  19. }
  20. cc._decorator.ccclass["isnew"] = true;
  21. let HeadMenu = {
  22. "node": "cc.Node",
  23. "list": "cc.ScrollView",
  24. "lb": "cc.Label",
  25. "spr": "cc.Sprite",
  26. "rich": "cc.RichText",
  27. "btn": "cc.Button",
  28. "bar": "cc.ProgressBar",
  29. "edit": "cc.EditBox",
  30. "video": "cc.VideoPlayer",
  31. "bone": "dragonBones.ArmatureDisplay",
  32. "spine": "sp.Skeleton"
  33. };
  34. let replace = '\n\
  35. /*自动生成*/\n\
  36. @property({type:{type}, displayName:""})\n\
  37. private {name}:{type} = null;\n\
  38. ';
  39. const PattStart = '===========================自动绑定组件开始===========================';
  40. const PattEnd = '===========================自动绑定组件结束===========================';
  41. const PattStart_BTN = '===========================自动生成按钮事件开始==========================';
  42. const PattEnd_BTN = '===========================自动生成按钮事件结束==========================';
  43. //缓存全部需要绑定的节点
  44. let caches = {};
  45. let oldCodeMap = {}
  46. let oldCodeFunction = {}
  47. module.exports = {
  48. // 新建ts模板路径
  49. getTsTemplatePath(){
  50. return Editor.url(`packages://${PACKAGE_NAME}/${scriptTemplate}`, 'utf8')
  51. },
  52. print:function(nodes){
  53. for (const key in nodes) {
  54. const node = nodes[key];
  55. Editor.log(key,node);
  56. }
  57. },
  58. /**
  59. * 等待3次检查 要检查绑定了才刷新
  60. */
  61. async bindRef(data){
  62. await wait(1000);
  63. this.bindSciprt(data);
  64. await wait(1000);
  65. this.bindSciprt(data);
  66. //刷新脚本
  67. Editor.assetdb.refresh(data.jsPath,(err, results)=>{
  68. if(err){
  69. data.event.reply(null,"刷新失败"+err);
  70. }else{
  71. data.event.reply("刷新成功",results);
  72. }
  73. });
  74. Editor.Scene.callSceneScript(PACKAGE_NAME, 'add-script',function(error,obj){
  75. if(obj){
  76. return Editor.log(obj);
  77. }
  78. //执行回来的操作
  79. });
  80. },
  81. /**
  82. * 绑定脚本到Node上
  83. */
  84. bindSciprt(data){
  85. let node = this.findNode(data.uuid)
  86. let comp = node.getComponent(data.jsFileName);
  87. if(!comp){
  88. try {
  89. comp = cc.require(data.jsFileName)
  90. } catch(t) {}
  91. // 添加组件
  92. if (comp){
  93. node.addComponent(data.jsFileName);
  94. }
  95. }
  96. },
  97. async createTs(node,event){
  98. let self = this;
  99. let uuid = node.uuid;
  100. let file_type = ".ts";
  101. let jsFileName = node.name;
  102. jsFileName = StringUtil.firstLowerCase(jsFileName);
  103. let templateTs = fs.readFileSync(this.getTsTemplatePath(),'utf-8');
  104. //获取当前节点的路径
  105. let obj = await this.getCurrSceneUrl();
  106. let url = this.getNewFileSavePath(obj.url);
  107. //创建文件夹
  108. this.createDir(Editor.url(url));
  109. templateTs = templateTs.replace(/ScriptTemplate/g, jsFileName);
  110. let jsPath = `${url}${jsFileName}${file_type}`;
  111. //Editor.log("创建脚本位置",jsPath)
  112. Editor.assetdb.createOrSave( jsPath, templateTs, ( err, results )=>
  113. {
  114. if(err) return event.reply(null,"创建脚本失败");
  115. self.bindRef({
  116. uuid:uuid,
  117. node:node,
  118. jsFileName:jsFileName,
  119. jsPath:jsPath,
  120. event:event
  121. });
  122. });
  123. },
  124. /**
  125. * 添加脚本
  126. * @param {*} event
  127. * @param {{ uuids: string[], name: string }} data 数据
  128. */
  129. 'add-script':async function(event) {
  130. oldCodeMap = {}
  131. let obj = await this.getCurrSceneUrl();
  132. if(!obj) return event.reply("当前没有选中任何节点");
  133. let self = this;
  134. // 发消息给主进程进行关键词匹配
  135. let file_type = localStorage.getItem("newFileType")|| ".ts"
  136. //当前选中的Node
  137. let head_node = Editor.Selection.curSelection('node')[0];
  138. //当前选中Node之下的全部节点
  139. let nodes = this.findNode(head_node);
  140. //只挂载脚本
  141. let jsFile = this.isHasJsFile(nodes);
  142. if (jsFile)
  143. {
  144. let node_uids = Editor.Selection.curSelection("node");
  145. //Editor.log(node_uids, node_uids.length);
  146. for (let i = 0; i < node_uids.length; i++) {
  147. let id = node_uids[i];
  148. Editor.Scene.callSceneScript(PACKAGE_NAME, "auto_code_binder", id);
  149. }
  150. }else
  151. {
  152. //创建挂载脚本
  153. this.createTs(nodes,event);
  154. }
  155. },
  156. /**
  157. * 生成代码属性
  158. *
  159. * 属性 写入文件
  160. */
  161. "auto_code_binder":function(event,uuid){
  162. caches = {};
  163. let node = cc.engine.getInstanceById(uuid);
  164. if (!node) {
  165. Editor.log("没有找到需要的节点");
  166. return;
  167. }
  168. //开始整个文件查找节点
  169. this.findChildren(node);
  170. //获取组件脚本绑定
  171. let coms = node.getComponents(cc.Component);
  172. if(coms && coms.length <= 0){
  173. Editor.warn("该节点下没有脚本组件");
  174. return;
  175. }
  176. for (let com of coms) {
  177. let name = cc.js.getClassName(com);
  178. let btns = {};
  179. //排除系统组件
  180. if (!name.startsWith("cc.") && !name.startsWith("sp.")) {
  181. // Editor.warn("自定义脚本",name);
  182. let comUuid = com.__scriptUuid;
  183. let path = Editor.remote.assetdb.uuidToFspath(comUuid);
  184. let tsScriptData = fs.readFileSync(path, "utf-8");
  185. let patt = new RegExp(`${PattStart}[\\s\\S]*${PattEnd}\*`, "gm");
  186. let res = tsScriptData.match(patt);
  187. let data = res === null || res === void 0 ? void 0 : res.toString();
  188. // Editor.warn("代码内容",data);
  189. //结束
  190. if (!res) {
  191. Editor.log("=====脚本中没有发现替代注释 请在需要添加组件的地方复制粘贴以下注释======");
  192. Editor.log(`/*${PattStart}*/`);
  193. Editor.log(`/*${PattEnd}*/`);
  194. return;
  195. }
  196. this.parseText(data);
  197. let str = `${PattStart}*/`;
  198. let seq = 0;
  199. let funcSeq = 0;
  200. for (const key in caches) {
  201. const node = caches[key];
  202. if(node){
  203. let n = this.getHeadString(key);
  204. let type = HeadMenu[n];
  205. if(type == "cc.Button"){
  206. btns[key] = key;
  207. }
  208. if(!type){
  209. Editor.warn("发现不知道的类型",key);
  210. continue;
  211. }
  212. let old = oldCodeMap[key];
  213. if (old) {
  214. if(seq == 0){str += "\n"}
  215. str += "\t" + old.content + "\n";
  216. seq +=1;
  217. }else{
  218. let txmp = replace;
  219. if (type) {
  220. txmp = txmp.replace(new RegExp(/({type})/g,"gm"),type)
  221. txmp = txmp.replace("{name}", key);
  222. }
  223. str += txmp;
  224. }
  225. }
  226. }
  227. str += "\n\t/*" + PattEnd + "";
  228. tsScriptData = tsScriptData.replace(res.toString(), str);
  229. //生成按钮方法属性
  230. let patt_btn = new RegExp(`${PattStart_BTN}[\\s\\S]*${PattEnd_BTN}\*`, "gm");
  231. let result = tsScriptData.match(patt_btn);
  232. let btnFuncs = result === null || result === void 0 ? void 0 : result.toString();
  233. //结束
  234. if (!btnFuncs) {
  235. //Editor.log("=====脚本中没有发现替代注释 请在需要添加组件的地方复制粘贴以下注释======");
  236. //Editor.log(`/*${PattStart}*/`);
  237. //Editor.log(`/*${PattEnd}*/`);
  238. return;
  239. }
  240. oldCodeFunction = {};
  241. this.parseFunction(btnFuncs);
  242. let newStr = `${PattStart_BTN}*/\n`;
  243. for (const key in btns) {
  244. const name = btns[key];
  245. let funcName = `on${this.getFunctionString(name)}TouchEnd`;
  246. let old = oldCodeFunction[funcName];
  247. if (old) {
  248. if(funcSeq == 0){newStr += "\n"}
  249. newStr += "\t" + old + "\n";
  250. funcSeq +=1;
  251. }else{
  252. let str = `\t${funcName}(){}\n\n`
  253. newStr += str ;
  254. }
  255. }
  256. newStr += "\n\t/*" + PattEnd_BTN;
  257. tsScriptData = tsScriptData.replace(result.toString(), newStr);
  258. fs.writeFileSync(path, tsScriptData);
  259. this.waitRefreshScript(comUuid,node);
  260. }
  261. }
  262. },
  263. parseFunction(data){
  264. if (!data) {
  265. return;
  266. }
  267. let reg = data.match(/on*([\s\S]*?)}/g);
  268. if (!reg) {
  269. return;
  270. }
  271. for (let i = 0; i < (reg === null || reg === void 0 ? void 0 : reg.length); i++) {
  272. let str = reg[i];
  273. let type = str.match(/on([\s\S]*?)TouchEnd/)[0];
  274. if (!type) {
  275. continue;
  276. }
  277. oldCodeFunction[type] = str;
  278. }
  279. },
  280. parseText(data){
  281. if (!data) {
  282. return;
  283. }
  284. let reg = data.match(/\/\*自动生成\*\/([\s\S]*?);/g);
  285. if (!reg) {
  286. //Editor.log("没有匹配");
  287. return;
  288. }
  289. for (let i = 0; i < (reg === null || reg === void 0 ? void 0 : reg.length); i++) {
  290. let str = reg[i];
  291. let type = str.match(/type:([\s\S]*?),/)[0];
  292. if (!type) {
  293. continue;
  294. }
  295. type = type === null || type === void 0 ? void 0 : type.replace("type:", "");
  296. type = type === null || type === void 0 ? void 0 : type.replace(",", "");
  297. type = type.trim();
  298. let name = str.match(/(private|public)([\s\S]*?):/)[0];
  299. if (!name) {
  300. continue;
  301. }
  302. name = name === null || name === void 0 ? void 0 : name.replace(/(private|public)/, "");
  303. name = name === null || name === void 0 ? void 0 : name.replace(":", "");
  304. name = name.trim();
  305. let old = {};
  306. old.content = str;
  307. old.name = name;
  308. old.type = type;
  309. oldCodeMap[name] = old;
  310. }
  311. },
  312. //执行刷新
  313. async waitRefreshScript(comUuid,node){
  314. let obj = await this.getCurrSceneUrl();
  315. let assetPath = Editor.remote.assetdb.uuidToUrl(comUuid);
  316. Editor.assetdb.refresh(assetPath);
  317. Editor.assetdb.refresh(obj.url);
  318. //wait(2000)
  319. //Editor.Scene.callSceneScript(PACKAGE_NAME, "bind_Code_ByNode");
  320. },
  321. //生成属性到脚本
  322. "generate_attribute":function(){
  323. let head_node = Editor.Selection.curSelection('node')[0];
  324. if(!head_node) return Editor.log("没有选中节点");
  325. //当前选中Node之下的全部节点
  326. let nodes = this.findNode(head_node);
  327. //只挂载脚本
  328. let jsFile = this.isHasJsFile(nodes);
  329. if (jsFile)
  330. {
  331. //Editor.log("当前有脚本",jsFile.__classname__)
  332. let node_uids = Editor.Selection.curSelection("node");
  333. Editor.log(node_uids, node_uids.length);
  334. for (let i = 0; i < node_uids.length; i++) {
  335. let id = node_uids[i];
  336. Editor.Scene.callSceneScript(PACKAGE_NAME, "auto_code_binder", id);
  337. }
  338. }else
  339. {
  340. //创建挂载脚本
  341. Editor.log("当前节点没有挂载节点")
  342. }
  343. },
  344. /**
  345. * 绑定节点到代码
  346. */
  347. "bind_Code_ByNode":async function(event){
  348. let uuid = Editor.Selection.curSelection('node');
  349. let node = cc.engine.getInstanceById(uuid);
  350. caches = {};
  351. if (!node) {
  352. Editor.log("没有找到需要的节点");
  353. return;
  354. }
  355. //开始整个文件查找节点
  356. this.findChildren(node);
  357. this.bindCodeByNode();
  358. },
  359. //开始关联
  360. bindCodeByNode(){
  361. let uuid = Editor.Selection.curSelection('node');
  362. let node = cc.engine.getInstanceById(uuid);
  363. if(!node)return Editor.warn("====请选择一个节========"); ;
  364. //记录绑定信息
  365. let coms = node.getComponents(cc.Component);
  366. for (let com of coms) {
  367. let name = cc.js.getClassName(com);
  368. //过滤
  369. if (name.startsWith("cc.") || name.startsWith("sp.")) { continue };
  370. let comUuid = com.__scriptUuid;
  371. let cache = com.constructor[TOOL_CACHE_KEY];
  372. if(!cache){
  373. return;
  374. }
  375. for (let key in com) {
  376. if (caches[key]) {
  377. if(key == "constructor") continue;
  378. let data = cache.proto.properties[key];
  379. let children = caches[key];
  380. if (children) {
  381. let nodes = children.filter(node => {
  382. if (data.type == cc.Node) {
  383. return node;
  384. }
  385. return node.getComponent(data.type);
  386. });
  387. if (nodes && nodes.length) {
  388. let first = nodes[0];
  389. let needCom = null;
  390. if (data.type == cc.Node) {
  391. needCom = first;
  392. } else {
  393. needCom = first.getComponent(data.type);
  394. }
  395. com[key] = needCom;
  396. //按钮统一格式
  397. if(cc.Button == data.type){
  398. let clickEvents = needCom.clickEvents;
  399. //查找是否已经绑定过了
  400. let funcName = `on${this.getFunctionString(key)}TouchEnd`
  401. let isBind = false;
  402. //查找是否绑定了规定函数
  403. for (let index = 0; index < clickEvents.length; index++) {
  404. const clickEvent = clickEvents[index];
  405. if(clickEvent.handler == funcName){
  406. isBind = true;
  407. }
  408. }
  409. //绑定自动生成的函数
  410. if(!isBind){
  411. var clickEventHandler = new cc.Component.EventHandler();
  412. //这个 node 节点是你的事件处理代码组件所属的节点
  413. clickEventHandler.target = node;
  414. clickEventHandler.component = name;//这个是代码文件名
  415. clickEventHandler.handler = funcName; //回调函数
  416. needCom.clickEvents.push(clickEventHandler);
  417. }
  418. //设置默认的按钮样式
  419. // needCom
  420. needCom.transition = cc.Button.Transition.SCALE;
  421. needCom.duration = 0.1;
  422. needCom.zoomScale = 1.2;
  423. }
  424. continue;
  425. }
  426. }
  427. }
  428. }
  429. let assetPath = Editor.remote.assetdb.uuidToUrl(comUuid);
  430. Editor.assetdb.refresh(assetPath);
  431. //Editor.Ipc.sendToMain('assets:open-text-file', comUuid);
  432. }
  433. Editor.log("=======绑定结束========");
  434. },
  435. getHeadString:function(str, fortmat = "_") {
  436. if (!str) {
  437. return str;
  438. }
  439. let res = str.split(fortmat);
  440. return res[1];
  441. },
  442. getFunctionString:function(str, fortmat = "_") {
  443. if (!str) {
  444. return str;
  445. }
  446. let res = str.split(fortmat);
  447. let name = res[0]
  448. if(name.indexOf("$") > -1){
  449. name = name.substring(name.indexOf("$")+1,name.length);
  450. return StringUtil.firstUpperCase(name);
  451. }
  452. return StringUtil.firstUpperCase(name);
  453. },
  454. findChildren:function(node) {
  455. let children = node.children;
  456. for (let i = 0; i < children.length; i++) {
  457. let child = children[i];
  458. if(child.name.indexOf("@") > -1) continue;
  459. let idx = child.name.indexOf("$");
  460. if(idx > -1){
  461. if(caches[child.name] == null){
  462. caches[child.name] = [];
  463. }
  464. caches[child.name].push(child);
  465. }
  466. if(child.childrenCount > 0){
  467. this.findChildren(child)
  468. }
  469. }
  470. },
  471. dump(obj){
  472. let str = ""
  473. let i = 0
  474. for(let name in obj) {
  475. try{
  476. str += name + ": " + obj[name] + "\n"
  477. }catch(err){
  478. str += name + ": " + typeof obj[name] + "\n"
  479. }
  480. i ++;
  481. if (i>100){
  482. str+="...more"
  483. break;
  484. }
  485. }
  486. Editor.log("dump:"+obj,str)
  487. },
  488. // 保存新建脚本路径
  489. getNewFileSavePath(node_url){
  490. // 如果是预制体 脚本就生成到Sciprt下
  491. if(node_url.lastIndexOf(".prefab") > -1){
  492. let str = node_url.substr(0,node_url.lastIndexOf("/"))
  493. return str.replace("prefab","Script") + "/";
  494. }else{
  495. return node_url.substr(0,node_url.lastIndexOf("/")) + '/'; //保存到预制节点的同级目录
  496. }
  497. // return node_url.substr(0,node_url.lastIndexOf("/")) + '/';
  498. },
  499. // 创建目录,绝对路径
  500. createDir(dirPath){
  501. if ( fs.existsSync(dirPath) ) return;
  502. let paths = dirPath.split(path.sep);//分割路径
  503. let path_ = "";
  504. for (let n = 0; n < paths.length; n++) {
  505. path_ += paths[n] + path.sep;
  506. if(!fs.existsSync(path_)){
  507. fs.mkdirSync(path_);
  508. }
  509. }
  510. },
  511. // 获得当前打开的场景文件路径
  512. async getCurrSceneUrl(callFunc){
  513. return new Promise((resolve)=>{
  514. let obj = {};
  515. let scene = cc.director.getScene();
  516. if(scene){
  517. let url = Editor.remote.assetdb.uuidToUrl(scene.uuid);
  518. if (url){
  519. obj.url = url
  520. obj.isScene = true
  521. obj.uuid = scene.uuid;
  522. return resolve(obj);
  523. }else{
  524. // 当前打开的预制节点路径
  525. Editor.Ipc.sendToMain(`${PACKAGE_NAME}:getPrefabUuid`,{}, function (error, answer)
  526. {
  527. if (answer != null){
  528. obj.url = Editor.remote.assetdb.uuidToUrl(answer)
  529. obj.isScene = false
  530. obj.uuid = answer;
  531. return resolve(obj);
  532. }else{
  533. resolve(null);
  534. }
  535. });
  536. }
  537. }else{
  538. // 当前打开的预制节点路径
  539. Editor.Ipc.sendToMain(`${PACKAGE_NAME}:getPrefabUuid`,{}, function (error, answer)
  540. {
  541. if (answer != null){
  542. obj.url = Editor.remote.assetdb.uuidToUrl(answer)
  543. obj.isScene = false
  544. obj.uuid = answer;
  545. return resolve(obj);
  546. }else{
  547. resolve(null);
  548. }
  549. });
  550. }
  551. })
  552. },
  553. // 检测场景是否存在该子节点并返回相关信息
  554. findNode(select_uuid)
  555. {
  556. var canvas = cc.director.getScene();
  557. var ret_node
  558. if (canvas && select_uuid) {
  559. this.getNodeChildren(canvas,(node)=>{
  560. if (node.uuid == select_uuid){
  561. ret_node = node;
  562. return ret_node;
  563. }
  564. })
  565. }
  566. return ret_node;
  567. },
  568. // 遍历所有深层子节点
  569. getNodeChildren(node,callFunc)
  570. {
  571. if (!node) return;
  572. let nodes = node.getChildren();
  573. nodes.forEach((v)=>{
  574. v._path_str = (node._path_str || node.name)+"/" + v.name;
  575. this.getNodeChildren(v,callFunc)
  576. });
  577. callFunc(node)
  578. },
  579. getNodeReChildren(node,callFunc)
  580. {
  581. if (!node) return;
  582. let nodes = node.getChildren();
  583. callFunc(node)
  584. nodes.forEach((v)=>{
  585. v._path_str = (node._path_str || node.name)+"/" + v.name;
  586. this.getNodeReChildren(v,callFunc)
  587. });
  588. },
  589. isHasJsFile(node){
  590. if(!node) {return false};
  591. return this.getJsFileList(node)[0];
  592. },
  593. getJsFileList(node){
  594. if(!node) {return []};
  595. let list = [];
  596. node.getComponents(cc.Component).forEach((v)=>{
  597. if(v.__classname__ && v.__classname__.indexOf(".") == -1) list.push(v); //js脚本
  598. });
  599. return list;
  600. },
  601. }