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