Newer
Older
DungeonShooting / DungeonShooting_Godot / addons / dungeonShooting_plugin / generator / UiGenerator.cs
@小李xl 小李xl on 15 Feb 2024 18 KB 小修改
  1. #if TOOLS
  2.  
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Text.RegularExpressions;
  7. using Godot;
  8.  
  9. namespace Generator;
  10.  
  11. /// <summary>
  12. /// Ui类生成器
  13. /// </summary>
  14. public static class UiGenerator
  15. {
  16. private static Dictionary<string, int> _nodeNameMap = new Dictionary<string, int>();
  17. private static int _nestedIndex = 1;
  18.  
  19. /// <summary>
  20. /// 根据名称在编辑器中创建Ui, open 表示创建完成后是否在编辑器中打开这个ui
  21. /// </summary>
  22. public static bool CreateUi(string uiName, bool open = false)
  23. {
  24. try
  25. {
  26. //创建脚本代码
  27. var scriptPath = GameConfig.UiCodeDir + uiName.FirstToLower();
  28. var scriptFile = scriptPath + "/" + uiName + "Panel.cs";
  29. var scriptCode = $"using Godot;\n" +
  30. $"\n" +
  31. $"namespace UI.{uiName};\n" +
  32. $"\n" +
  33. $"public partial class {uiName}Panel : {uiName}\n" +
  34. $"{{\n" +
  35. $"\n" +
  36. $" public override void OnCreateUi()\n" +
  37. $" {{\n" +
  38. $" \n" +
  39. $" }}\n" +
  40. $"\n" +
  41. $" public override void OnDestroyUi()\n" +
  42. $" {{\n" +
  43. $" \n" +
  44. $" }}\n" +
  45. $"\n" +
  46. $"}}\n";
  47. if (!Directory.Exists(scriptPath))
  48. {
  49. Directory.CreateDirectory(scriptPath);
  50. }
  51. File.WriteAllText(scriptFile, scriptCode);
  52.  
  53. //加载脚本资源
  54. var scriptRes = GD.Load<CSharpScript>("res://" + scriptFile);
  55.  
  56. //创建场景资源
  57. var prefabFile = GameConfig.UiPrefabDir + uiName + ".tscn";
  58. var prefabResPath = "res://" + prefabFile;
  59. if (!Directory.Exists(GameConfig.UiPrefabDir))
  60. {
  61. Directory.CreateDirectory(GameConfig.UiPrefabDir);
  62. }
  63. var uiNode = new Control();
  64. uiNode.Name = uiName;
  65. uiNode.SetAnchorsPreset(Control.LayoutPreset.FullRect, true);
  66. uiNode.SetScript(scriptRes);
  67. var scene = new PackedScene();
  68. scene.Pack(uiNode);
  69. ResourceSaver.Save(scene, prefabResPath);
  70. //生成Ui结构代码
  71. GenerateUiCode(uiNode, scriptPath + "/" + uiName + ".cs");
  72.  
  73. //生成 ResourcePath.cs 代码
  74. ResourcePathGenerator.Generate();
  75. //生成 UiManager_Methods.cs 代码
  76. UiManagerMethodsGenerator.Generate();
  77.  
  78. //打开ui
  79. if (open)
  80. {
  81. EditorInterface.Singleton.OpenSceneFromPath(prefabResPath);
  82. }
  83.  
  84. }
  85. catch (Exception e)
  86. {
  87. GD.PrintErr(e.ToString());
  88. return false;
  89. }
  90.  
  91. return true;
  92. }
  93.  
  94. /// <summary>
  95. /// 根据指定ui节点生成相应的Ui类, 并保存到指定路径下
  96. /// </summary>
  97. public static void GenerateUiCode(Node control, string path)
  98. {
  99. _nodeNameMap.Clear();
  100. _nestedIndex = 1;
  101. var uiNode = EachNodeFromEditor(control.Name, control);
  102. var code = GenerateClassCode(uiNode);
  103. File.WriteAllText(path, code);
  104. }
  105.  
  106. /// <summary>
  107. /// 从编辑器中生成ui代码
  108. /// </summary>
  109. public static bool GenerateUiCodeFromEditor(Node control)
  110. {
  111. try
  112. {
  113. _nodeNameMap.Clear();
  114. _nestedIndex = 1;
  115. var uiName = control.Name.ToString();
  116. var path = GameConfig.UiCodeDir + uiName.FirstToLower() + "/" + uiName + ".cs";
  117. GD.Print("重新生成ui代码: " + path);
  118.  
  119. var uiNode = EachNodeFromEditor(control.Name, control);
  120. var code = GenerateClassCode(uiNode);
  121. foreach (var pair in _nodeNameMap)
  122. {
  123. if (pair.Value > 1)
  124. {
  125. GD.Print($"检测到同名节点: '{pair.Key}', 使用该名称的节点将无法生成唯一节点属性!");
  126. }
  127. }
  128. File.WriteAllText(path, code);
  129. }
  130. catch (Exception e)
  131. {
  132. GD.PrintErr(e.ToString());
  133. return false;
  134. }
  135.  
  136. return true;
  137. }
  138.  
  139. private static string GenerateClassCode(UiNodeInfo uiNodeInfo)
  140. {
  141. var retraction = " ";
  142. return $"namespace UI.{uiNodeInfo.OriginName};\n\n" +
  143. $"/// <summary>\n" +
  144. $"/// Ui代码, 该类是根据ui场景自动生成的, 请不要手动编辑该类, 以免造成代码丢失\n" +
  145. $"/// </summary>\n" +
  146. $"public abstract partial class {uiNodeInfo.OriginName} : UiBase\n" +
  147. $"{{\n" +
  148. GeneratePropertyListClassCode("", uiNodeInfo.OriginName + ".", uiNodeInfo, retraction) +
  149. $"\n" +
  150. $" public {uiNodeInfo.OriginName}() : base(nameof({uiNodeInfo.OriginName}))\n" +
  151. $" {{\n" +
  152. $" }}\n" +
  153. $"\n" +
  154. $" public sealed override void OnInitNestedUi()\n" +
  155. $" {{\n" +
  156. GenerateUiScriptCode("", uiNodeInfo, retraction) +
  157. $"\n" +
  158. GenerateInitNestedUi("", uiNodeInfo, retraction) +
  159. $" }}\n" +
  160. $"\n" +
  161. GenerateAllChildrenClassCode(uiNodeInfo.OriginName + ".", uiNodeInfo, retraction) +
  162. $"\n" +
  163. GenerateSoleLayerCode(uiNodeInfo, "", uiNodeInfo.OriginName, retraction) +
  164. $"}}\n";
  165. }
  166.  
  167. private static string GenerateUiScriptCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  168. {
  169. var str = "";
  170. var node = parent + (string.IsNullOrEmpty(parent) ? "" : ".") + uiNodeInfo.Name;
  171. if (uiNodeInfo.IsNodeScript)
  172. {
  173. str += retraction + $" _ = {node};\n";
  174. }
  175.  
  176. if (uiNodeInfo.Children != null)
  177. {
  178. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  179. {
  180. var item = uiNodeInfo.Children[i];
  181. if (uiNodeInfo.OriginName == uiNodeInfo.UiRootName)
  182. {
  183. str += GenerateUiScriptCode("", item, retraction);
  184. }
  185. else
  186. {
  187. str += GenerateUiScriptCode(node, item, retraction);
  188. }
  189. }
  190. }
  191.  
  192. return str;
  193. }
  194.  
  195. private static string GenerateInitNestedUi(string parent, UiNodeInfo uiNodeInfo, string retraction)
  196. {
  197. var str = "";
  198. if (uiNodeInfo.IsRefUi)
  199. {
  200. var parentUi = "inst" + _nestedIndex++;
  201. var uiNode = $"{parentUi}.{uiNodeInfo.Name}.Instance";
  202. var parentNode = string.IsNullOrEmpty(parent) ? "this" : parent;
  203. var parentNode2 = string.IsNullOrEmpty(parent) ? "null" : parentUi;
  204. str += retraction + $" var {parentUi} = {parentNode};\n";
  205. str += retraction + $" RecordNestedUi({uiNode}, {parentNode2}, UiManager.RecordType.Open);\n";
  206. str += retraction + $" {uiNode}.OnCreateUi();\n";
  207. str += retraction + $" {uiNode}.OnInitNestedUi();\n\n";
  208. }
  209. else
  210. {
  211. if (uiNodeInfo.Children != null)
  212. {
  213. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  214. {
  215. var item = uiNodeInfo.Children[i];
  216. if (uiNodeInfo.OriginName == uiNodeInfo.UiRootName)
  217. {
  218. str += GenerateInitNestedUi("", item, retraction);
  219. }
  220. else
  221. {
  222. str += GenerateInitNestedUi(parent + (string.IsNullOrEmpty(parent)? "" : ".") + uiNodeInfo.Name, item, retraction);
  223. }
  224. }
  225. }
  226. }
  227.  
  228. return str;
  229. }
  230. private static string GenerateAllChildrenClassCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  231. {
  232. var str = "";
  233. if (uiNodeInfo.Children != null)
  234. {
  235. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  236. {
  237. var item = uiNodeInfo.Children[i];
  238. str += GenerateAllChildrenClassCode(parent + item.OriginName + ".", item, retraction);
  239. str += GenerateChildrenClassCode(parent, item, retraction);
  240. }
  241. }
  242.  
  243. return str;
  244. }
  245. private static string GenerateChildrenClassCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  246. {
  247. string cloneCode;
  248.  
  249. if (uiNodeInfo.IsRefUi)
  250. {
  251. cloneCode = retraction + $" public override {uiNodeInfo.ClassName} Clone()\n";
  252. cloneCode += retraction + $" {{\n";
  253. cloneCode += retraction + $" var uiNode = new {uiNodeInfo.ClassName}(UiPanel, ({uiNodeInfo.NodeTypeName})Instance.Duplicate());\n";
  254. cloneCode += retraction + $" UiPanel.RecordNestedUi(uiNode.Instance, this, UiManager.RecordType.Open);\n";
  255. cloneCode += retraction + $" uiNode.Instance.OnCreateUi();\n";
  256. cloneCode += retraction + $" uiNode.Instance.OnInitNestedUi();\n";
  257. cloneCode += retraction + $" return uiNode;\n";
  258. cloneCode += retraction + $" }}\n";
  259. }
  260. else
  261. {
  262. cloneCode = retraction + $" public override {uiNodeInfo.ClassName} Clone() => new (UiPanel, ({uiNodeInfo.NodeTypeName})Instance.Duplicate());\n";
  263. }
  264. return retraction + $"/// <summary>\n" +
  265. retraction + $"/// 类型: <see cref=\"{uiNodeInfo.NodeTypeName}\"/>, 路径: {parent}{uiNodeInfo.OriginName}\n" +
  266. retraction + $"/// </summary>\n" +
  267. retraction + $"public class {uiNodeInfo.ClassName} : UiNode<{uiNodeInfo.UiRootName}Panel, {uiNodeInfo.NodeTypeName}, {uiNodeInfo.ClassName}>\n" +
  268. retraction + $"{{\n" +
  269. GeneratePropertyListClassCode("Instance.", parent, uiNodeInfo, retraction + " ") +
  270. retraction + $" public {uiNodeInfo.ClassName}({uiNodeInfo.UiRootName}Panel uiPanel, {uiNodeInfo.NodeTypeName} node) : base(uiPanel, node) {{ }}\n" +
  271. cloneCode +
  272. retraction + $"}}\n\n";
  273. }
  274.  
  275. private static string GeneratePropertyListClassCode(string target, string parent, UiNodeInfo uiNodeInfo, string retraction)
  276. {
  277. var str = "";
  278. if (uiNodeInfo.Children != null)
  279. {
  280. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  281. {
  282. var item = uiNodeInfo.Children[i];
  283. str += GeneratePropertyCode(target, parent, item, retraction);
  284. }
  285. }
  286.  
  287. return str;
  288. }
  289. private static string GeneratePropertyCode(string target, string parent, UiNodeInfo uiNodeInfo, string retraction)
  290. {
  291. string uiPanel;
  292. if (string.IsNullOrEmpty(target))
  293. {
  294. uiPanel = $"({uiNodeInfo.UiRootName}Panel)this";
  295. }
  296. else
  297. {
  298. uiPanel = "UiPanel";
  299. }
  300. return retraction + $"/// <summary>\n" +
  301. retraction + $"/// 使用 Instance 属性获取当前节点实例对象, 节点类型: <see cref=\"{uiNodeInfo.NodeTypeName}\"/>, 节点路径: {parent}{uiNodeInfo.OriginName}\n" +
  302. retraction + $"/// </summary>\n" +
  303. retraction + $"public {uiNodeInfo.ClassName} {uiNodeInfo.Name}\n" +
  304. retraction + $"{{\n" +
  305. retraction + $" get\n" +
  306. retraction + $" {{\n" +
  307. retraction + $" if (_{uiNodeInfo.Name} == null) _{uiNodeInfo.Name} = new {uiNodeInfo.ClassName}({uiPanel}, {target}GetNode<{uiNodeInfo.NodeTypeName}>(\"{uiNodeInfo.OriginName}\"));\n" +
  308. retraction + $" return _{uiNodeInfo.Name};\n" +
  309. retraction + $" }}\n" +
  310. retraction + $"}}\n" +
  311. retraction + $"private {uiNodeInfo.ClassName} _{uiNodeInfo.Name};\n\n";
  312. }
  313.  
  314. private static string GenerateSoleLayerCode(UiNodeInfo uiNodeInfo, string layerName, string parent, string retraction)
  315. {
  316. var str = "";
  317. if (uiNodeInfo.Children != null)
  318. {
  319. foreach (var nodeInfo in uiNodeInfo.Children)
  320. {
  321. var layer = layerName;
  322. if (layerName.Length > 0)
  323. {
  324. layer += ".";
  325. }
  326.  
  327. layer += nodeInfo.Name;
  328. var path = parent + "." + nodeInfo.OriginName;
  329. str += GenerateSoleLayerCode(nodeInfo, layer, path, retraction);
  330. if (IsSoleNameNode(nodeInfo))
  331. {
  332. str += $"{retraction}/// <summary>\n";
  333. str += $"{retraction}/// 场景中唯一名称的节点, 节点类型: <see cref=\"{nodeInfo.NodeTypeName}\"/>, 节点路径: {path}\n";
  334. str += $"{retraction}/// </summary>\n";
  335. str += $"{retraction}public {nodeInfo.ClassName} S_{nodeInfo.OriginName} => {layer};\n\n";
  336. }
  337. }
  338. }
  339. return str;
  340. }
  341.  
  342. //返回指定节点在当前场景中是否是唯一名称的节点
  343. private static bool IsSoleNameNode(UiNodeInfo uiNodeInfo)
  344. {
  345. if (!_nodeNameMap.TryGetValue(uiNodeInfo.OriginName, out var count))
  346. {
  347. return true;
  348. }
  349.  
  350. return count <= 1;
  351. }
  352.  
  353. /// <summary>
  354. /// 在编辑器下递归解析节点, 由于编辑器下绑定用户脚本的节点无法直接判断节点类型, 那么就只能获取节点的脚本然后解析名称和命名空间
  355. /// </summary>
  356. private static UiNodeInfo EachNodeFromEditor(string uiRootName, Node node)
  357. {
  358. UiNodeInfo uiNode;
  359. //原名称
  360. var originName = Regex.Replace(node.Name, "[^\\w]", "");
  361. //字段名称
  362. var fieldName = "L_" + originName;
  363. //类定义该图层的类名
  364. string className;
  365. if (_nodeNameMap.ContainsKey(originName)) //有同名图层, 为了防止类名冲突, 需要在 UiNode 后面加上索引
  366. {
  367. var count = _nodeNameMap[originName];
  368. className = originName + "_" + count;
  369. _nodeNameMap[originName] = count + 1;
  370. }
  371. else
  372. {
  373. className = originName;
  374. _nodeNameMap.Add(originName, 1);
  375. }
  376.  
  377. var script = node.GetScript().As<CSharpScript>();
  378. if (script == null) //无脚本, 说明是内置节点
  379. {
  380. uiNode = new UiNodeInfo(uiRootName, fieldName, originName, className, node.GetType().FullName, false);
  381. }
  382. else //存在脚本
  383. {
  384. var index = script.ResourcePath.LastIndexOf("/", StringComparison.Ordinal);
  385. //文件名称
  386. var fileName = script.ResourcePath.Substring(index + 1, script.ResourcePath.Length - index - 3 - 1);
  387. //在源代码中寻找命名空间
  388. var match = Regex.Match(script.SourceCode, "(?<=namespace\\s+)[\\w\\.]+");
  389. bool isNodeScript;
  390. if (match.Success) //存在命名空间
  391. {
  392. isNodeScript = CheckNodeScript(match.Value + "." + fileName);
  393. uiNode = new UiNodeInfo(uiRootName, fieldName, originName, className, match.Value + "." + fileName, isNodeScript);
  394. }
  395. else //不存在命名空间
  396. {
  397. isNodeScript = CheckNodeScript(fileName);
  398. uiNode = new UiNodeInfo(uiRootName, fieldName, originName, className, fileName, isNodeScript);
  399. }
  400. //检测是否是引用Ui
  401. if (fileName.EndsWith("Panel"))
  402. {
  403. var childUiName = fileName.Substring(0, fileName.Length - 5);
  404. if (childUiName != uiRootName && File.Exists(GameConfig.UiPrefabDir + childUiName + ".tscn"))
  405. {
  406. //是引用Ui
  407. uiNode.IsRefUi = true;
  408. }
  409. }
  410. }
  411. //如果是引用Ui, 就没有必要递归子节点了
  412. if (uiNode.IsRefUi)
  413. {
  414. return uiNode;
  415. }
  416. var childCount = node.GetChildCount();
  417. if (childCount > 0)
  418. {
  419. for (var i = 0; i < childCount; i++)
  420. {
  421. var children = node.GetChild(i);
  422. if (children != null)
  423. {
  424. if (uiNode.Children == null)
  425. {
  426. uiNode.Children = new List<UiNodeInfo>();
  427. }
  428.  
  429. uiNode.Children.Add(EachNodeFromEditor(uiRootName, children));
  430. }
  431. }
  432. }
  433. return uiNode;
  434. }
  435.  
  436. private static bool CheckNodeScript(string typeName)
  437. {
  438. var type = typeof(UiGenerator).Assembly.GetType(typeName);
  439. if (type == null)
  440. {
  441. return false;
  442. }
  443.  
  444. return type.IsAssignableTo(typeof(IUiNodeScript));
  445. }
  446.  
  447. private class UiNodeInfo
  448. {
  449. /// <summary>
  450. /// 层级名称
  451. /// </summary>
  452. public string Name;
  453. /// <summary>
  454. /// 层级原名称
  455. /// </summary>
  456. public string OriginName;
  457. /// <summary>
  458. /// 层级类名
  459. /// </summary>
  460. public string ClassName;
  461. /// <summary>
  462. /// Godot节点类型名称
  463. /// </summary>
  464. public string NodeTypeName;
  465. /// <summary>
  466. /// 子节点
  467. /// </summary>
  468. public List<UiNodeInfo> Children;
  469. /// <summary>
  470. /// Ui根节点名称
  471. /// </summary>
  472. public string UiRootName;
  473. /// <summary>
  474. /// 是否实现了 IUiNodeScript 接口
  475. /// </summary>
  476. public bool IsNodeScript;
  477. /// <summary>
  478. /// 是否是引用Ui
  479. /// </summary>
  480. public bool IsRefUi;
  481. public UiNodeInfo(string uiRootName, string name, string originName, string className, string nodeTypeName, bool isNodeScript)
  482. {
  483. UiRootName = uiRootName;
  484. Name = name;
  485. OriginName = originName;
  486. ClassName = className;
  487. NodeTypeName = nodeTypeName;
  488. IsNodeScript = isNodeScript;
  489. if (isNodeScript)
  490. {
  491. GD.Print("发现 IUiNodeScript 节点: " + originName);
  492. }
  493. }
  494. }
  495. }
  496.  
  497. #endif