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