Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / generator / UiGenerator.cs
@小李xl 小李xl on 31 Mar 2023 12 KB 更新文档
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text.RegularExpressions;
  6. using Godot;
  7.  
  8. namespace Generator;
  9.  
  10. /// <summary>
  11. /// Ui类生成器
  12. /// </summary>
  13. public static class UiGenerator
  14. {
  15. private static Dictionary<string, int> _nodeNameMap = new Dictionary<string, int>();
  16.  
  17. /// <summary>
  18. /// 根据名称在编辑器中创建Ui, open 表示创建完成后是否在编辑器中打开这个ui
  19. /// </summary>
  20. public static bool CreateUi(string uiName, bool open = false)
  21. {
  22. try
  23. {
  24. //创建脚本代码
  25. var scriptPath = GameConfig.UiCodeDir + uiName.FirstToLower();
  26. var scriptFile = scriptPath + "/" + uiName + "Panel.cs";
  27. var scriptCode = $"using Godot;\n" +
  28. $"\n" +
  29. $"namespace UI.{uiName};\n" +
  30. $"\n" +
  31. $"public partial class {uiName}Panel : {uiName}\n" +
  32. $"{{\n" +
  33. $"\n" +
  34. $" public override void OnShowUi()\n" +
  35. $" {{\n" +
  36. $" \n" +
  37. $" }}\n" +
  38. $"\n" +
  39. $" public override void OnHideUi()\n" +
  40. $" {{\n" +
  41. $" \n" +
  42. $" }}\n" +
  43. $"\n" +
  44. $"}}\n";
  45. if (!Directory.Exists(scriptPath))
  46. {
  47. Directory.CreateDirectory(scriptPath);
  48. }
  49. File.WriteAllText(scriptFile, scriptCode);
  50.  
  51. //加载脚本资源
  52. var scriptRes = GD.Load<CSharpScript>("res://" + scriptFile);
  53.  
  54. //创建场景资源
  55. var prefabFile = GameConfig.UiPrefabDir + uiName + ".tscn";
  56. var prefabResPath = "res://" + prefabFile;
  57. if (!Directory.Exists(GameConfig.UiPrefabDir))
  58. {
  59. Directory.CreateDirectory(GameConfig.UiPrefabDir);
  60. }
  61. var uiNode = new Control();
  62. uiNode.Name = uiName;
  63. uiNode.SetAnchorsPreset(Control.LayoutPreset.FullRect, true);
  64. uiNode.SetScript(scriptRes);
  65. var scene = new PackedScene();
  66. scene.Pack(uiNode);
  67. ResourceSaver.Save(scene, prefabResPath);
  68. //生成Ui结构代码
  69. GenerateUiCode(uiNode, scriptPath + "/" + uiName + ".cs");
  70.  
  71. //生成 ResourcePath.cs 代码
  72. ResourcePathGenerator.Generate();
  73. //生成 UiManager_Methods.cs 代码
  74. UiManagerMethodsGenerator.Generate();
  75. #if TOOLS
  76. //打开ui
  77. if (open)
  78. {
  79. Plugin.Plugin.Instance.GetEditorInterface().OpenSceneFromPath(prefabResPath);
  80. }
  81. #endif
  82. }
  83. catch (Exception e)
  84. {
  85. GD.PrintErr(e.ToString());
  86. return false;
  87. }
  88.  
  89. return true;
  90. }
  91.  
  92. /// <summary>
  93. /// 根据指定ui节点生成相应的Ui类, 并保存到指定路径下
  94. /// </summary>
  95. public static void GenerateUiCode(Node control, string path)
  96. {
  97. _nodeNameMap.Clear();
  98. var uiNode = EachNode(control);
  99. var code = GenerateClassCode(uiNode);
  100. File.WriteAllText(path, code);
  101. }
  102.  
  103. /// <summary>
  104. /// 从编辑器中生成ui代码
  105. /// </summary>
  106. public static bool GenerateUiCodeFromEditor(Node control)
  107. {
  108. try
  109. {
  110. _nodeNameMap.Clear();
  111. var uiName = control.Name.ToString();
  112. var path = GameConfig.UiCodeDir + uiName.FirstToLower() + "/" + uiName + ".cs";
  113. GD.Print("重新生成ui代码: " + path);
  114.  
  115. var uiNode = EachNodeFromEditor(control);
  116. var code = GenerateClassCode(uiNode);
  117. File.WriteAllText(path, code);
  118. }
  119. catch (Exception e)
  120. {
  121. GD.PrintErr(e.ToString());
  122. return false;
  123. }
  124.  
  125. return true;
  126. }
  127.  
  128. private static string GenerateClassCode(UiNodeInfo uiNodeInfo)
  129. {
  130. return $"namespace UI.{uiNodeInfo.OriginName};\n\n" +
  131. $"/// <summary>\n" +
  132. $"/// Ui代码, 该类是根据ui场景自动生成的, 请不要手动编辑该类, 以免造成代码丢失\n" +
  133. $"/// </summary>\n" +
  134. $"public abstract partial class {uiNodeInfo.OriginName} : UiBase\n" +
  135. $"{{\n" +
  136. GeneratePropertyListClassCode("", uiNodeInfo.OriginName + ".", uiNodeInfo, " ") +
  137. $"\n" +
  138. $" public {uiNodeInfo.OriginName}() : base(nameof({uiNodeInfo.OriginName}))\n" +
  139. $" {{\n" +
  140. $" }}\n" +
  141. $"\n" +
  142. GenerateAllChildrenClassCode(uiNodeInfo.OriginName + ".", uiNodeInfo, " ") +
  143. $"}}\n";
  144. }
  145.  
  146. private static string GenerateAllChildrenClassCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  147. {
  148. var str = "";
  149. if (uiNodeInfo.Children != null)
  150. {
  151. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  152. {
  153. var item = uiNodeInfo.Children[i];
  154. str += GenerateAllChildrenClassCode(parent + item.OriginName + ".", item, retraction);
  155. str += GenerateChildrenClassCode(parent, item, retraction);
  156. }
  157. }
  158.  
  159. return str;
  160. }
  161. private static string GenerateChildrenClassCode(string parent, UiNodeInfo uiNodeInfo, string retraction)
  162. {
  163. return retraction + $"/// <summary>\n" +
  164. retraction + $"/// 类型: <see cref=\"{uiNodeInfo.TypeName}\"/>, 路径: {parent}{uiNodeInfo.OriginName}\n" +
  165. retraction + $"/// </summary>\n" +
  166. retraction + $"public class {uiNodeInfo.ClassName} : IUiNode<{uiNodeInfo.TypeName}, {uiNodeInfo.ClassName}>\n" +
  167. retraction + $"{{\n" +
  168. GeneratePropertyListClassCode("Instance.", parent, uiNodeInfo, retraction + " ") +
  169. retraction + $" public {uiNodeInfo.ClassName}({uiNodeInfo.TypeName} node) : base(node) {{ }}\n" +
  170. retraction + $" public override {uiNodeInfo.ClassName} Clone() => new (({uiNodeInfo.TypeName})Instance.Duplicate());\n" +
  171. retraction + $"}}\n\n";
  172. }
  173.  
  174. private static string GeneratePropertyListClassCode(string target, string parent, UiNodeInfo uiNodeInfo, string retraction)
  175. {
  176. var str = "";
  177. if (uiNodeInfo.Children != null)
  178. {
  179. for (var i = 0; i < uiNodeInfo.Children.Count; i++)
  180. {
  181. var item = uiNodeInfo.Children[i];
  182. str += GeneratePropertyCode(target, parent, item, retraction);
  183. }
  184. }
  185.  
  186. return str;
  187. }
  188. private static string GeneratePropertyCode(string target, string parent, UiNodeInfo uiNodeInfo, string retraction)
  189. {
  190. return retraction + $"/// <summary>\n" +
  191. retraction + $"/// 使用 Instance 属性获取当前节点实例对象, 节点类型: <see cref=\"{uiNodeInfo.TypeName}\"/>, 节点路径: {parent}{uiNodeInfo.OriginName}\n" +
  192. retraction + $"/// </summary>\n" +
  193. retraction + $"public {uiNodeInfo.ClassName} {uiNodeInfo.Name}\n" +
  194. retraction + $"{{\n" +
  195. retraction + $" get\n" +
  196. retraction + $" {{\n" +
  197. retraction + $" if (_{uiNodeInfo.Name} == null) _{uiNodeInfo.Name} = new {uiNodeInfo.ClassName}({target}GetNodeOrNull<{uiNodeInfo.TypeName}>(\"{uiNodeInfo.OriginName}\"));\n" +
  198. retraction + $" return _{uiNodeInfo.Name};\n" +
  199. retraction + $" }}\n" +
  200. retraction + $"}}\n" +
  201. retraction + $"private {uiNodeInfo.ClassName} _{uiNodeInfo.Name};\n\n";
  202. }
  203. /// <summary>
  204. /// 递归解析节点, 并生成 UiNodeInfo 数据
  205. /// </summary>
  206. private static UiNodeInfo EachNode(Node node)
  207. {
  208. var originName = Regex.Replace(node.Name, "[^\\w]", "");
  209. //类定义该图层的类名
  210. string className;
  211. if (_nodeNameMap.ContainsKey(originName)) //有同名图层, 为了防止类名冲突, 需要在 UiNode 后面加上索引
  212. {
  213. var count = _nodeNameMap[originName];
  214. className = "UiNode" + (count) + "_" + originName;
  215. _nodeNameMap[originName] = count + 1;
  216. }
  217. else
  218. {
  219. className = "UiNode" + "_" + originName;
  220. _nodeNameMap.Add(originName, 1);
  221. }
  222. var uiNode = new UiNodeInfo("L_" + originName, originName, className, node.GetType().FullName);
  223.  
  224. var childCount = node.GetChildCount();
  225. if (childCount > 0)
  226. {
  227. for (var i = 0; i < childCount; i++)
  228. {
  229. var children = node.GetChild(i);
  230. if (children != null)
  231. {
  232. if (uiNode.Children == null)
  233. {
  234. uiNode.Children = new List<UiNodeInfo>();
  235. }
  236.  
  237. uiNode.Children.Add(EachNode(children));
  238. }
  239. }
  240. }
  241.  
  242. return uiNode;
  243. }
  244.  
  245. /// <summary>
  246. /// 在编辑器下递归解析节点, 由于编辑器下绑定用户脚本的节点无法直接判断节点类型, 那么就只能获取节点的脚本然后解析名称和命名空间
  247. /// </summary>
  248. private static UiNodeInfo EachNodeFromEditor(Node node)
  249. {
  250. UiNodeInfo uiNode;
  251. //原名称
  252. var originName = Regex.Replace(node.Name, "[^\\w]", "");
  253. //字段名称
  254. var fieldName = "L_" + originName;
  255. //类定义该图层的类名
  256. string className;
  257. if (_nodeNameMap.ContainsKey(originName)) //有同名图层, 为了防止类名冲突, 需要在 UiNode 后面加上索引
  258. {
  259. var count = _nodeNameMap[originName];
  260. className = "UiNode" + (count) + "_" + originName;
  261. _nodeNameMap[originName] = count + 1;
  262. }
  263. else
  264. {
  265. className = "UiNode" + "_" + originName;
  266. _nodeNameMap.Add(originName, 1);
  267. }
  268.  
  269. var script = node.GetScript().As<CSharpScript>();
  270. if (script == null) //无脚本, 说明是内置节点
  271. {
  272. uiNode = new UiNodeInfo(fieldName, originName, className, node.GetType().FullName);
  273. }
  274. else //存在脚本
  275. {
  276. var index = script.ResourcePath.LastIndexOf("/", StringComparison.Ordinal);
  277. //文件名称
  278. var fileName = script.ResourcePath.Substring(index + 1, script.ResourcePath.Length - index - 3 - 1);
  279. //在源代码中寻找命名空间
  280. var match = Regex.Match(script.SourceCode, "(?<=namespace\\s+)[\\w\\.]+");
  281. if (match.Success) //存在命名空间
  282. {
  283. uiNode = new UiNodeInfo(fieldName, originName, className, match.Value + "." + fileName);
  284. }
  285. else //不存在命名空间
  286. {
  287. uiNode = new UiNodeInfo(fieldName, originName, className, fileName);
  288. }
  289. }
  290. var childCount = node.GetChildCount();
  291. if (childCount > 0)
  292. {
  293. for (var i = 0; i < childCount; i++)
  294. {
  295. var children = node.GetChild(i);
  296. if (children != null)
  297. {
  298. if (uiNode.Children == null)
  299. {
  300. uiNode.Children = new List<UiNodeInfo>();
  301. }
  302.  
  303. uiNode.Children.Add(EachNodeFromEditor(children));
  304. }
  305. }
  306. }
  307. return uiNode;
  308. }
  309.  
  310. private class UiNodeInfo
  311. {
  312. public string Name;
  313. public string OriginName;
  314. public string ClassName;
  315. public string TypeName;
  316. public List<UiNodeInfo> Children;
  317.  
  318. public UiNodeInfo(string name, string originName, string className, string typeName)
  319. {
  320. Name = name;
  321. OriginName = originName;
  322. ClassName = className;
  323. TypeName = typeName;
  324. }
  325. }
  326. }