QuickUI.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. using UnityEngine;
  2. using System.Collections;
  3. using UnityEditor;
  4. using System.Collections.Generic;
  5. using UnityEngine.EventSystems;
  6. using UnityEngine.Events;
  7. using UnityEngine.UI;
  8. using System.Text;
  9. using System;
  10. using System.IO;
  11. using System.Reflection;
  12. /// <summary>
  13. /// 描述:编辑器插件,快速生成UI代码(只检索以特定文本开头的组件,img_图片 btn_按钮 txt_文本)
  14. /// 作者:WJ
  15. /// </summary>
  16. public class QuickUI
  17. {
  18. /// <summary>
  19. /// UI的根游戏体
  20. /// </summary>
  21. private static GameObject mRoot = null;
  22. /// <summary>
  23. /// ui控件列表
  24. /// </summary>
  25. private static List<UIBehaviour> mUIWidgets = new List<UIBehaviour>();
  26. /// <summary>
  27. /// 缓存所有变量名和对应控件对象,对重名作处理
  28. /// </summary>
  29. private static Dictionary<string, object> mVariableNameDic = new Dictionary<string, object>();
  30. /// <summary>
  31. /// 组件名称检索列表(只检索以列表开头命名的组件)
  32. /// </summary>
  33. private static List<string> mWidgetsNameSearchList = new List<string> { "btn_", "img_", "txt_" };
  34. /// <summary>
  35. /// 组件类型检索列表(只检索里的组件)
  36. /// </summary>
  37. private static List<string> mWidgetsTypeSearchList = new List<string> { "Button", "Image", "Text" };
  38. /// <summary>
  39. /// 变量编号
  40. /// </summary>
  41. private static int mVariableNum = 0;
  42. /// <summary>
  43. /// 变量代码
  44. /// </summary>
  45. private static StringBuilder mCodeStateText = null;
  46. /// <summary>
  47. /// 事件代码
  48. /// </summary>
  49. private static StringBuilder mCodeEventText = null;
  50. /// <summary>
  51. /// 初始化变量代码
  52. /// </summary>
  53. private static StringBuilder mCodeAssignText = null;
  54. /// <summary>
  55. /// 开始函数
  56. /// </summary>
  57. private static StringBuilder mStartText = null;
  58. /// <summary>
  59. /// 更新函数
  60. /// </summary>
  61. private static StringBuilder mUpdateText = null;
  62. /// <summary>
  63. /// 函数注释
  64. /// </summary>
  65. private static string funNotes = "\n\t/// <summary>\n\t/// {0}\n\t/// </summary>";
  66. /// <summary>
  67. /// 生成UI代码
  68. /// </summary>
  69. [MenuItem("Tools/GenerateUICode")]
  70. public static void GenerateUICode()
  71. {
  72. // 自动赋值选中的GameObject
  73. mRoot = Selection.activeGameObject;
  74. if (mRoot == null)
  75. {
  76. Debug.LogError("未选中UI根节点");
  77. return;
  78. }
  79. // 创建开始函数
  80. StartFunCode();
  81. // 创建更新函数
  82. UpdateFunCode();
  83. // 查找UI组件
  84. FindUIWidgets();
  85. // 生成UI组件变量
  86. GenerateVariable();
  87. // 给UI组件变量赋值
  88. AssignmentVariable();
  89. // 给UI组件绑定事件
  90. BuildEventCode();
  91. // 生成脚本
  92. CreateUIScript();
  93. }
  94. /// <summary>
  95. /// 查找UI组件
  96. /// </summary>
  97. private static void FindUIWidgets()
  98. {
  99. // 查找节点下所有UI元素
  100. RecursiveUI(mRoot.transform, (tran) =>
  101. {
  102. UIBehaviour[] widgets = tran.GetComponents<UIBehaviour>();
  103. for (int i = 0; i < widgets.Length; i++)
  104. {
  105. var widget = widgets[i];
  106. if (widget != null && !mUIWidgets.Contains(widget))
  107. {
  108. // 只检测特定类型
  109. Type type = widget.GetType();
  110. if (mWidgetsTypeSearchList.Contains(type.Name) == false)
  111. {
  112. continue;
  113. }
  114. // 图片组件中有按钮,则不添加
  115. if (type.Name == "Image")
  116. {
  117. Button button = widget.GetComponent<Button>();
  118. if (button != null)
  119. {
  120. continue;
  121. }
  122. }
  123. string widName = widget.name;
  124. for (int j = 0; j < mWidgetsNameSearchList.Count; j++)
  125. {
  126. if (widName.Contains(mWidgetsNameSearchList[j]))
  127. {
  128. mUIWidgets.Add(widget);
  129. break;
  130. }
  131. }
  132. }
  133. }
  134. });
  135. }
  136. /// <summary>
  137. /// 控件遍历声明,0:类型 1:名称
  138. /// </summary>
  139. private const string variableCodeFmt = "\tprivate {0} {1}; \n";
  140. /// <summary>
  141. /// 生成变量
  142. /// </summary>
  143. /// <returns></returns>
  144. private static string GenerateVariable()
  145. {
  146. mVariableNum = 0;
  147. mVariableNameDic.Clear();
  148. mCodeStateText = null;
  149. mCodeStateText = new StringBuilder();
  150. //控件列表
  151. for (int i = 0; i < mUIWidgets.Count; i++)
  152. {
  153. if (mUIWidgets[i] == null) continue;
  154. Type type = mUIWidgets[i].GetType();
  155. if (type == null)
  156. {
  157. Debug.LogError("BuildUICode type error !");
  158. return "";
  159. }
  160. string typeName = type.Name;
  161. string variableName = mUIWidgets[i].name;
  162. variableName = variableName.Replace(' ', '_'); //命名有空格的情况
  163. //重名处理
  164. ++mVariableNum;
  165. if (mVariableNameDic.ContainsKey(variableName))
  166. {
  167. variableName += mVariableNum;
  168. }
  169. mVariableNameDic.Add(variableName, mUIWidgets[i]);
  170. mCodeStateText.AppendFormat(variableCodeFmt, typeName, variableName);
  171. }
  172. return mCodeStateText.ToString();
  173. }
  174. /// <summary>
  175. /// 函数开始格式
  176. /// </summary>
  177. public const string funStartFmt = "\n\tprivate void {0}() \n\t{{\n";
  178. /// <summary>
  179. /// 函数结束格式
  180. /// </summary>
  181. public const string funEndFmt = "\n\t}\n";
  182. /// <summary>
  183. /// 查找组件相关格式
  184. /// </summary>
  185. public const string findCodeFmt = "\t\t{0} = transform.Find(\"{1}\").GetComponent<{2}>(); \n";
  186. public const string findGameObjectCodeFmt = "\t\t{0} = transform.Find(\"{1}\").gameObject; \n";
  187. public const string findRootCodeFmt = "\t\t{0} = transform.GetComponent<{1}>(); \n";
  188. /// <summary>
  189. /// 给变量赋值
  190. /// </summary>
  191. private static void AssignmentVariable()
  192. {
  193. mCodeAssignText = new StringBuilder();
  194. mCodeAssignText.AppendFormat(funNotes, "初始化UI");
  195. mCodeAssignText.AppendFormat(funStartFmt, "InitUI");
  196. var allPath = GetChildrenPaths(mRoot);
  197. if (mVariableNameDic == null)
  198. {
  199. return;
  200. }
  201. //格式:变量名 = transform.Find("").Getcomponent<>();
  202. foreach (var name in mVariableNameDic.Keys)
  203. {
  204. var obj = mVariableNameDic[name];
  205. if (obj == null) continue;
  206. string path = "";
  207. bool isRootComponent = false;
  208. foreach (var tran in allPath.Keys)
  209. {
  210. if (tran == null) continue;
  211. UIBehaviour behav = obj as UIBehaviour;
  212. if (behav != null)
  213. {
  214. //判断是否挂在根上,根上不需要路径
  215. isRootComponent = behav.gameObject == mRoot;
  216. if (isRootComponent) break;
  217. if (behav.gameObject == tran.gameObject)
  218. {
  219. path = allPath[tran];
  220. break;
  221. }
  222. }
  223. else
  224. {
  225. if (tran.gameObject == obj)
  226. {
  227. path = allPath[tran];
  228. break;
  229. }
  230. }
  231. }
  232. if (obj is GameObject)
  233. {
  234. mCodeAssignText.AppendFormat(findGameObjectCodeFmt, name, path);
  235. }
  236. else
  237. {
  238. if (isRootComponent)
  239. {
  240. mCodeAssignText.AppendFormat(findRootCodeFmt, name, obj.GetType().Name);
  241. }
  242. else
  243. {
  244. mCodeAssignText.AppendFormat(findCodeFmt, name, path, obj.GetType().Name);
  245. }
  246. }
  247. }
  248. mCodeAssignText.Append(funEndFmt);
  249. }
  250. // 按钮绑定事件
  251. public const string btnBindingEventCode = "\t\tEventTriggerListener.Get({0}.gameObject).onClick = On{1}Click; \n";
  252. public const string btnCallbackCode = "\n\tprivate void On{0}Click(GameObject go)\n\t{{\n\n\t}}\n";
  253. /// <summary>
  254. /// 构建注册控件事件的代码
  255. /// </summary>
  256. /// <returns></returns>
  257. private static string BuildEventCode()
  258. {
  259. mCodeEventText = null;
  260. mCodeEventText = new StringBuilder();
  261. mCodeEventText.AppendFormat(funNotes, "添加事件");
  262. mCodeEventText.AppendFormat(funStartFmt, "AddEvent");
  263. // 添加事件的列表
  264. List<string> EventWidgets = new List<string>();
  265. EventWidgets.Add("Button");
  266. bool hasEventWidget = false; //标识是否有控件注册了事件
  267. for (int i = 0; i < mUIWidgets.Count; i++)
  268. {
  269. if (mUIWidgets[i] == null) continue;
  270. //剔除不是事件或者是事件但未勾选toggle的控件
  271. string typeName = mUIWidgets[i].GetType().Name;
  272. foreach (var vName in mVariableNameDic.Keys)
  273. {
  274. if (mUIWidgets[i].Equals(mVariableNameDic[vName]))
  275. {
  276. if (EventWidgets.Contains(typeName) == false)
  277. {
  278. continue;
  279. }
  280. string variableName = vName;
  281. if (!string.IsNullOrEmpty(variableName))
  282. {
  283. string methodName = variableName.Substring(variableName.IndexOf('_') + 1);
  284. if (mUIWidgets[i] is Button)
  285. {
  286. string onClickStr = string.Format(btnBindingEventCode, variableName, methodName);
  287. if (hasEventWidget)
  288. {
  289. string str = mCodeEventText.ToString();
  290. mCodeEventText.Insert(str.LastIndexOf(';') + 1, "\n" + onClickStr);
  291. }
  292. else
  293. {
  294. mCodeEventText.Append(onClickStr);
  295. }
  296. mCodeEventText.AppendFormat(funNotes, methodName + "按钮事件");
  297. mCodeEventText.AppendFormat(btnCallbackCode, methodName);
  298. hasEventWidget = true;
  299. }
  300. else
  301. {
  302. Debug.LogError("暂不支持的类型:" + mUIWidgets[i]);
  303. }
  304. }
  305. break;
  306. }
  307. }
  308. }
  309. string codeStr = mCodeEventText.ToString();
  310. if (hasEventWidget)
  311. {
  312. mCodeEventText.Insert(codeStr.LastIndexOf(';') + 1, funEndFmt);
  313. }
  314. else
  315. {
  316. mCodeEventText.Append(funEndFmt);
  317. }
  318. return mCodeEventText.ToString();
  319. }
  320. /// <summary>
  321. /// 开始函数代码
  322. /// </summary>
  323. private static void StartFunCode()
  324. {
  325. mStartText = new StringBuilder();
  326. mStartText.AppendFormat(funNotes, "开始");
  327. mStartText.AppendFormat(funStartFmt, "Start");
  328. mStartText.AppendFormat("\t\tInitUI();\n");
  329. mStartText.AppendFormat("\t\tAddEvent();");
  330. mStartText.Append(funEndFmt);
  331. }
  332. /// <summary>
  333. /// 更新函数代码
  334. /// </summary>
  335. private static void UpdateFunCode()
  336. {
  337. mUpdateText = new StringBuilder();
  338. mUpdateText.AppendFormat(funNotes, "更新");
  339. mUpdateText.AppendFormat(funStartFmt, "Update");
  340. mUpdateText.Append(funEndFmt);
  341. }
  342. /// <summary>
  343. /// UI 类名
  344. /// </summary>
  345. private static string mClassName = "";
  346. /// <summary>
  347. /// 类注释
  348. /// </summary>
  349. public const string mCodeAnnotation = @"
  350. /// <summary>
  351. /// 描述:
  352. /// 作者:
  353. /// </summary>";
  354. /// <summary>
  355. /// 类默认使用的命名空间
  356. /// </summary>
  357. public const string mUsingNamespace = "using UnityEngine;\nusing System.Collections;\nusing UnityEngine.UI;\n";
  358. /// <summary>
  359. /// 继承MonoBehaviour的类开始格式
  360. /// </summary>
  361. public const string mClassMonoStart = "\npublic class {0} : MonoBehaviour\n{{\n";
  362. /// <summary>
  363. /// 类结束格式
  364. /// </summary>
  365. public const string mClassEnd = "\n}\n";
  366. /// <summary>
  367. /// 生成C# UI脚本
  368. /// </summary>
  369. private static void CreateUIScript()
  370. {
  371. string path = EditorPrefs.GetString("create_script_folder", "");
  372. path = EditorUtility.SaveFilePanel("Create Script", path, mRoot.name + ".cs", "cs");
  373. if (string.IsNullOrEmpty(path)) return;
  374. int index = path.LastIndexOf('/');
  375. mClassName = path.Substring(index + 1, path.LastIndexOf('.') - index - 1);
  376. StringBuilder scriptBuilder = new StringBuilder();
  377. scriptBuilder.Append(mUsingNamespace);
  378. scriptBuilder.Append(mCodeAnnotation);
  379. scriptBuilder.AppendFormat(mClassMonoStart, mClassName);
  380. scriptBuilder.Append(mCodeStateText);
  381. scriptBuilder.Append(mStartText);
  382. scriptBuilder.Append(mCodeAssignText);
  383. scriptBuilder.Append(mCodeEventText);
  384. scriptBuilder.Append(mUpdateText);
  385. scriptBuilder.Append(mClassEnd);
  386. File.WriteAllText(path, scriptBuilder.ToString(), new UTF8Encoding(false));
  387. AssetDatabase.Refresh();
  388. Debug.Log("脚本生成成功,生成路径为:" + path);
  389. EditorPrefs.SetString("create_script_folder", path);
  390. }
  391. /// <summary>
  392. /// 遍历UI
  393. /// </summary>
  394. /// <param name="parent">父节点</param>
  395. /// <param name="callback">回调</param>
  396. public static void RecursiveUI(Transform parent, UnityAction<Transform> callback)
  397. {
  398. if (callback != null)
  399. {
  400. callback(parent);
  401. }
  402. if (parent.childCount >= 0)
  403. {
  404. for (int i = 0; i < parent.childCount; i++)
  405. {
  406. Transform child = parent.GetChild(i);
  407. RecursiveUI(child, callback);
  408. }
  409. }
  410. }
  411. /// <summary>
  412. /// 获取子节点的路径
  413. /// </summary>
  414. /// <param name="rootGo">GameObject</param>
  415. /// <returns>路径</returns>
  416. private static Dictionary<Transform, string> GetChildrenPaths(GameObject rootGo)
  417. {
  418. Dictionary<Transform, string> pathDic = new Dictionary<Transform, string>();
  419. string path = string.Empty;
  420. Transform[] tfArray = rootGo.GetComponentsInChildren<Transform>(true);
  421. for (int i = 0; i < tfArray.Length; i++)
  422. {
  423. Transform node = tfArray[i];
  424. string str = node.name;
  425. while (node.parent != null && node.gameObject != rootGo && node.parent.gameObject != rootGo)
  426. {
  427. str = string.Format("{0}/{1}", node.parent.name, str);
  428. node = node.parent;
  429. }
  430. path += string.Format("{0}\n", str);
  431. if (!pathDic.ContainsKey(tfArray[i]))
  432. {
  433. pathDic.Add(tfArray[i], str);
  434. }
  435. }
  436. return pathDic;
  437. }
  438. }