Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / generator / UiGenerator.cs
@小李xl 小李xl on 7 Aug 2023 16 KB 更新Ui生成器
  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. Plugin.Plugin.Instance.GetEditorInterface().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. GenerateInitNestedUi("", uiNodeInfo, retraction) +
  157. $" }}\n" +
  158. $"\n" +
  159. GenerateAllChildrenClassCode(uiNodeInfo.OriginName + ".", uiNodeInfo, retraction) +
  160. $"\n" +
  161. GenerateSoleLayerCode(uiNodeInfo, "", uiNodeInfo.OriginName, retraction) +
  162. $"}}\n";
  163. }
  164.  
  165. private static string GenerateInitNestedUi(string parent, UiNodeInfo uiNodeInfo, string retraction)
  166. {
  167. var str = "";
  168. if (uiNodeInfo.IsRefUi)
  169. {
  170. var parentUi = "inst" + _nestedIndex++;
  171. var uiNode = $"{parentUi}.{uiNodeInfo.Name}.Instance";
  172. var parentNode = string.IsNullOrEmpty(parent) ? "this" : parent;
  173. var parentNode2 = string.IsNullOrEmpty(parent) ? "null" : parentUi;
  174. str += retraction + $" var {parentUi} = {parentNode};\n";
  175. str += retraction + $" RecordNestedUi({uiNode}, {parentNode2}, UiManager.RecordType.Open);\n";
  176. str += retraction + $" {uiNode}.OnCreateUi();\n";
  177. str += retraction + $" {uiNode}.OnInitNestedUi();\n\n";
  178. }
  179. else
  180. {
  181. if (uiNodeInfo.Children != null)
  182. {
  183. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  184. {
  185. var item = uiNodeInfo.Children[i];
  186. if (uiNodeInfo.OriginName == uiNodeInfo.UiRootName)
  187. {
  188. str += GenerateInitNestedUi("", item, retraction);
  189. }
  190. else
  191. {
  192. str += GenerateInitNestedUi(parent + (string.IsNullOrEmpty(parent)? "" : ".") + uiNodeInfo.Name, item, retraction);
  193. }
  194. }
  195. }
  196. }
  197.  
  198. return str;
  199. }
  200. private static string GenerateAllChildrenClassCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  201. {
  202. var str = "";
  203. if (uiNodeInfo.Children != null)
  204. {
  205. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  206. {
  207. var item = uiNodeInfo.Children[i];
  208. str += GenerateAllChildrenClassCode(parent + item.OriginName + ".", item, retraction);
  209. str += GenerateChildrenClassCode(parent, item, retraction);
  210. }
  211. }
  212.  
  213. return str;
  214. }
  215. private static string GenerateChildrenClassCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  216. {
  217. string cloneCode;
  218.  
  219. if (uiNodeInfo.IsRefUi)
  220. {
  221. cloneCode = retraction + $" public override {uiNodeInfo.ClassName} Clone()\n";
  222. cloneCode += retraction + $" {{\n";
  223. cloneCode += retraction + $" var uiNode = new {uiNodeInfo.ClassName}(UiPanel, ({uiNodeInfo.NodeTypeName})Instance.Duplicate());\n";
  224. cloneCode += retraction + $" UiPanel.RecordNestedUi(uiNode.Instance, this, UiManager.RecordType.Open);\n";
  225. cloneCode += retraction + $" uiNode.Instance.OnCreateUi();\n";
  226. cloneCode += retraction + $" uiNode.Instance.OnInitNestedUi();\n";
  227. cloneCode += retraction + $" return uiNode;\n";
  228. cloneCode += retraction + $" }}\n";
  229. }
  230. else
  231. {
  232. cloneCode = retraction + $" public override {uiNodeInfo.ClassName} Clone() => new (UiPanel, ({uiNodeInfo.NodeTypeName})Instance.Duplicate());\n";
  233. }
  234. return retraction + $"/// <summary>\n" +
  235. retraction + $"/// 类型: <see cref=\"{uiNodeInfo.NodeTypeName}\"/>, 路径: {parent}{uiNodeInfo.OriginName}\n" +
  236. retraction + $"/// </summary>\n" +
  237. retraction + $"public class {uiNodeInfo.ClassName} : UiNode<{uiNodeInfo.UiRootName}Panel, {uiNodeInfo.NodeTypeName}, {uiNodeInfo.ClassName}>\n" +
  238. retraction + $"{{\n" +
  239. GeneratePropertyListClassCode("Instance.", parent, uiNodeInfo, retraction + " ") +
  240. retraction + $" public {uiNodeInfo.ClassName}({uiNodeInfo.UiRootName}Panel uiPanel, {uiNodeInfo.NodeTypeName} node) : base(uiPanel, node) {{ }}\n" +
  241. cloneCode +
  242. retraction + $"}}\n\n";
  243. }
  244.  
  245. private static string GeneratePropertyListClassCode(string target, string parent, UiNodeInfo uiNodeInfo, string retraction)
  246. {
  247. var str = "";
  248. if (uiNodeInfo.Children != null)
  249. {
  250. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  251. {
  252. var item = uiNodeInfo.Children[i];
  253. str += GeneratePropertyCode(target, parent, item, retraction);
  254. }
  255. }
  256.  
  257. return str;
  258. }
  259. private static string GeneratePropertyCode(string target, string parent, UiNodeInfo uiNodeInfo, string retraction)
  260. {
  261. string uiPanel;
  262. if (string.IsNullOrEmpty(target))
  263. {
  264. uiPanel = $"({uiNodeInfo.UiRootName}Panel)this";
  265. }
  266. else
  267. {
  268. uiPanel = "UiPanel";
  269. }
  270. return retraction + $"/// <summary>\n" +
  271. retraction + $"/// 使用 Instance 属性获取当前节点实例对象, 节点类型: <see cref=\"{uiNodeInfo.NodeTypeName}\"/>, 节点路径: {parent}{uiNodeInfo.OriginName}\n" +
  272. retraction + $"/// </summary>\n" +
  273. retraction + $"public {uiNodeInfo.ClassName} {uiNodeInfo.Name}\n" +
  274. retraction + $"{{\n" +
  275. retraction + $" get\n" +
  276. retraction + $" {{\n" +
  277. retraction + $" if (_{uiNodeInfo.Name} == null) _{uiNodeInfo.Name} = new {uiNodeInfo.ClassName}({uiPanel}, {target}GetNodeOrNull<{uiNodeInfo.NodeTypeName}>(\"{uiNodeInfo.OriginName}\"));\n" +
  278. retraction + $" return _{uiNodeInfo.Name};\n" +
  279. retraction + $" }}\n" +
  280. retraction + $"}}\n" +
  281. retraction + $"private {uiNodeInfo.ClassName} _{uiNodeInfo.Name};\n\n";
  282. }
  283.  
  284. private static string GenerateSoleLayerCode(UiNodeInfo uiNodeInfo, string layerName, string parent, string retraction)
  285. {
  286. var str = "";
  287. if (uiNodeInfo.Children != null)
  288. {
  289. foreach (var nodeInfo in uiNodeInfo.Children)
  290. {
  291. var layer = layerName;
  292. if (layerName.Length > 0)
  293. {
  294. layer += ".";
  295. }
  296.  
  297. layer += nodeInfo.Name;
  298. var path = parent + "." + nodeInfo.OriginName;
  299. str += GenerateSoleLayerCode(nodeInfo, layer, path, retraction);
  300. if (IsSoleNameNode(nodeInfo))
  301. {
  302. str += $"{retraction}/// <summary>\n";
  303. str += $"{retraction}/// 场景中唯一名称的节点, 节点类型: <see cref=\"{nodeInfo.NodeTypeName}\"/>, 节点路径: {path}\n";
  304. str += $"{retraction}/// </summary>\n";
  305. str += $"{retraction}public {nodeInfo.ClassName} S_{nodeInfo.OriginName} => {layer};\n\n";
  306. }
  307. }
  308. }
  309. return str;
  310. }
  311.  
  312. //返回指定节点在当前场景中是否是唯一名称的节点
  313. private static bool IsSoleNameNode(UiNodeInfo uiNodeInfo)
  314. {
  315. if (!_nodeNameMap.TryGetValue(uiNodeInfo.OriginName, out var count))
  316. {
  317. return true;
  318. }
  319.  
  320. return count <= 1;
  321. }
  322.  
  323. /// <summary>
  324. /// 在编辑器下递归解析节点, 由于编辑器下绑定用户脚本的节点无法直接判断节点类型, 那么就只能获取节点的脚本然后解析名称和命名空间
  325. /// </summary>
  326. private static UiNodeInfo EachNodeFromEditor(string uiRootName, Node node)
  327. {
  328. UiNodeInfo uiNode;
  329. //原名称
  330. var originName = Regex.Replace(node.Name, "[^\\w]", "");
  331. //字段名称
  332. var fieldName = "L_" + originName;
  333. //类定义该图层的类名
  334. string className;
  335. if (_nodeNameMap.ContainsKey(originName)) //有同名图层, 为了防止类名冲突, 需要在 UiNode 后面加上索引
  336. {
  337. var count = _nodeNameMap[originName];
  338. className = originName + "_" + count;
  339. _nodeNameMap[originName] = count + 1;
  340. }
  341. else
  342. {
  343. className = originName;
  344. _nodeNameMap.Add(originName, 1);
  345. }
  346.  
  347. var script = node.GetScript().As<CSharpScript>();
  348. if (script == null) //无脚本, 说明是内置节点
  349. {
  350. uiNode = new UiNodeInfo(uiRootName, fieldName, originName, className, node.GetType().FullName);
  351. }
  352. else //存在脚本
  353. {
  354. var index = script.ResourcePath.LastIndexOf("/", StringComparison.Ordinal);
  355. //文件名称
  356. var fileName = script.ResourcePath.Substring(index + 1, script.ResourcePath.Length - index - 3 - 1);
  357. //在源代码中寻找命名空间
  358. var match = Regex.Match(script.SourceCode, "(?<=namespace\\s+)[\\w\\.]+");
  359. if (match.Success) //存在命名空间
  360. {
  361. uiNode = new UiNodeInfo(uiRootName, fieldName, originName, className, match.Value + "." + fileName);
  362. }
  363. else //不存在命名空间
  364. {
  365. uiNode = new UiNodeInfo(uiRootName, fieldName, originName, className, fileName);
  366. }
  367. //检测是否是引用Ui
  368. if (fileName.EndsWith("Panel"))
  369. {
  370. var childUiName = fileName.Substring(0, fileName.Length - 5);
  371. if (childUiName != uiRootName && File.Exists(GameConfig.UiPrefabDir + childUiName + ".tscn"))
  372. {
  373. //是引用Ui
  374. uiNode.IsRefUi = true;
  375. }
  376. }
  377. }
  378. //如果是引用Ui, 就没有必要递归子节点了
  379. if (uiNode.IsRefUi)
  380. {
  381. return uiNode;
  382. }
  383. var childCount = node.GetChildCount();
  384. if (childCount > 0)
  385. {
  386. for (var i = 0; i < childCount; i++)
  387. {
  388. var children = node.GetChild(i);
  389. if (children != null)
  390. {
  391. if (uiNode.Children == null)
  392. {
  393. uiNode.Children = new List<UiNodeInfo>();
  394. }
  395.  
  396. uiNode.Children.Add(EachNodeFromEditor(uiRootName, children));
  397. }
  398. }
  399. }
  400. return uiNode;
  401. }
  402.  
  403. private class UiNodeInfo
  404. {
  405. /// <summary>
  406. /// 层级名称
  407. /// </summary>
  408. public string Name;
  409. /// <summary>
  410. /// 层级原名称
  411. /// </summary>
  412. public string OriginName;
  413. /// <summary>
  414. /// 层级类名
  415. /// </summary>
  416. public string ClassName;
  417. /// <summary>
  418. /// Godot节点类型名称
  419. /// </summary>
  420. public string NodeTypeName;
  421. /// <summary>
  422. /// 子节点
  423. /// </summary>
  424. public List<UiNodeInfo> Children;
  425. /// <summary>
  426. /// Ui根节点名称
  427. /// </summary>
  428. public string UiRootName;
  429. /// <summary>
  430. /// 是否是引用Ui
  431. /// </summary>
  432. public bool IsRefUi;
  433. public UiNodeInfo(string uiRootName, string name, string originName, string className, string nodeTypeName)
  434. {
  435. UiRootName = uiRootName;
  436. Name = name;
  437. OriginName = originName;
  438. ClassName = className;
  439. NodeTypeName = nodeTypeName;
  440. }
  441. }
  442. }
  443.  
  444. #endif