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