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