自动化代码生成工具V1.0
你是否还在每日辛辛苦苦的敲重复的UI面板的代码
你是否还在每天在初始化方法里写一大堆Onclick或者OnValueChange
你是否还在每日无聊的为UI面板拖一大堆组件
别担心
- 要学习如何制作该工具,你需要拥有以下知识
- C#反射相关知识
- C#匿名函数(lamda表达式)相关知识
- C#文件读写相关知识
- Unity中UGUI相关知识
- Unity编辑器扩展有一丝的了解
明确需求,我需要创造一个自动化的通过面板对象写出一个面板类,并且能够在编辑器完成编译之后把新建的面板类挂载到面板对象上,并且将面板对象上的组件与面板类自动关联
那么先定一条规则,面板对象的名字和面板类名是一样的,之后函数中传入的path的字符串全都是面板对象的名字
新建一个面板类
首先你得得到你选择的对象,Unity中的Selection类提供了一个得到当前选中的物体的API
那么便可以开始书写函数了
[MenuItem("Tools/UI/CreateUIScript")]
private static void CreateUIClass()
{
GameObject[] gameObjects = Selection.gameObjects;
if(gameObjects.Length == 0)
{
Debug.LogError("你没有选中一个对象");
return;
}
GameObject panelObject = gameObjects[0];
if(panelObject.name.IndexOf("Panel") == -1)
{
Debug.LogError("你选中的不是一个面板");
return;
}
if(panelObject.GetComponent<BasePanel>() != null )
{
Debug.LogError("你选中了一个已经挂载了面板的对象");
return;
}
if (!Directory.Exists(Application.dataPath + "/Scripts/UI"))
{
Directory.CreateDirectory(Application.dataPath + "/Scripts/UI");
}
string className = panelObject.name;
string path = Application.dataPath + $"/Scripts/UI/{className}.cs";
}
给这个函数加一个MenuItem以在上方工具栏中创建一个对应的按钮
首先先进行判断,判断是否选中了物体,并且物体中名字是否包含Panel这个词总不会你创建面板对象不会加Panel这个词吧 ,然后判断物体是否存在面板脚本。
接着就是用代码新建一个面板类
用代码新建一个面板类其实很简单,因为面板类的脚本实质上也是一种文本文件,可读可写。
那么,可以尝试回忆一下自己写代码的过程,然后制作出自动写代码的函数
绝大部分写类的情况都是先引用命名空间,因此有了这个函数
private static void WriteClass(string path, string className)
{
File.WriteAllText(path, "using UnityEngine;\nusing UnityEngine.UI;\n\n" +
$"public class {className} : BasePanel\n{{\n");
}
实质上是一种字符串拼接,使用WriteAllText来进行文件覆盖或者新建文件
接着使用AppendAllText来追加字符串
因此就可以写出成员变量
private static void WriteMember(string path, string name, Type type, string accessModifier = "public")
{
File.AppendAllText(path, $" {accessModifier} {type.Name} {name};\n");
}
接着写初始化方法,并且在初始化方法里对UI输入事件进行监听(以Button举例)
private static void WriteMethod(string path, string methodName, string accessModifier, bool isoverride = false,string type = "void")
{
if(isoverride)
{
File.AppendAllText(path, $"\n {accessModifier} override {type} {methodName}()\n" +" {");
}
else
{
File.AppendAllText(path, $"\n {accessModifier} {type} {methodName}()\n" +" {");
}
}
private static void WriteButton(string path,string buttonName)
{
File.AppendAllText(path, $"\n {buttonName}.onClick.AddListener(() =>\n {{\n\n }});");
}
这个button的初始化等同于大家熟知的lamda表达式
然后一个类就写完了,接着补上括号即可(省略了)
然后可以调用AssetDatabase里的Refresh方法刷新Project窗口。
自动挂载脚本并关联
众所周知,Unity在创建了一个类之后,会进行编译,编译结束后新建的类才能够正常的使用。
那么,有没有方法能够让Unity编译完成后开始执行某一个函数呢。
答案是有的
关于DidReloadScripts特性
这个特性能够让函数在Unity编辑器完成编译之后就执行,它的重载可以设置函数执行的顺序,如果有多个带有这个特性的函数的话。
那么要得到新建的类,该如何去做呢。
这个时候必然就要用到反射了,因为在代码里我们并不知道这个新建的脚本的类型和它的成员变量,必须要去得到它的type才能够进行。
欲想得到Type,必先得到程序集,欲想得到程序集,则必须得到程序集里的类或者字符串。
都很简单。
你可以记住这个程序集的名字,一般都是Assembly-CSharp
或者,我们有一个面板基类,可以通过BasePanel的类型得到该程序集
然后通过面板对象的名字得到程序集中的该类Type
然后为选中的对象添加该脚本,使用Type来添加没想到天天被嫌弃的Type添加法这个时候有大用TwT
[DidReloadScripts()]
private static void AssignUI()
{
isCreated = EditorPrefs.GetBool("isCreated");
if (isCreated)
{
GameObject[] gameObjects = Selection.gameObjects;
if (gameObjects.Length == 0)
{
Debug.LogError("你没有选中一个对象");
return;
}
GameObject panelObject = gameObjects[0];
Assembly assembly = Assembly.GetAssembly(typeof(BasePanel));
Type panelType = assembly.GetType(panelObject.name);
if (panelType == null)
{
Debug.LogError("不存在对应名字的面板");
return;
}
Component panel = panelObject.AddComponent(panelType);
}
然后就是简单粗暴的赋值时间,首先得到这个面板类的所有成员变量信息,
再得到子类对象中的所有非Text和Image的UI组件然后通过名字的对应关系一一进行赋值。
FieldInfo[] fieldInfos = panelType.GetFields();
Button[] buttons = panelObject.GetComponentsInChildren<Button>();
foreach (FieldInfo fieldInfo in fieldInfos)
{
Type type = fieldInfo.FieldType;
if (fieldInfo.FieldType == typeof(Button))
{
for (int i = 0; i<buttons.Length; i++)
{
if (buttons[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, buttons[i]);
}
}
}
此处只拿Button举例子,其他的同理
但是有个问题,拥有DidReloadScripts特性的函数会在编译完成之后就执行一次,但是很多情况下我们并不希望它执行,因此需要加一个约束条件。很容易想到一个bool类型的标识来限制
但是bool类型的标识条件会在重新编译之后变回默认值(false) 如果你没写的话
那就用一种简单粗暴的方法,直接将bool类型的标识存储在本地,这样就不会随着编译而改变值了。
与PlayerPrefs一样,Unity编辑器也提供了一个名为EditorPrefs的类,能够快速读取的存储一个简单的值
其中也拥有SetBool和GetBool方法
EditorPrefs.SetBool(string key, bool value);
EditorPrefs.GetBool(string key);
那么我们只需要在创建类的函数结束的时候,把bool标识设为true,并且在挂载类的函数中判断标识是否为true,并在函数结束或者不满足函数条件的时候降标识设置为false,就可以限制住这个函数的执行。
使用方法
打开Unity
导入这个文件
一定要放在Editor文件夹下! 一定要放在Editor文件夹下! 一定要放在Editor文件夹下!
选择你的面板对象
然后点击上方的按钮
最终代码如下,祝大家用的愉快
using UnityEngine.UI;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Reflection;
using UnityEditor.Callbacks;
public class UITool
{
public static bool isCreated = false;
[MenuItem("Tools/UI/CreateUIScript")]
private static void CreateUIClass()
{
GameObject[] gameObjects = Selection.gameObjects;
if(gameObjects.Length == 0)
{
Debug.LogError("你没有选中一个对象");
return;
}
GameObject panelObject = gameObjects[0];
if(panelObject.name.IndexOf("Panel") == -1)
{
Debug.LogError("你选中的不是一个面板");
return;
}
if(panelObject.GetComponent<BasePanel>() != null )
{
Debug.LogError("你选中了一个已经挂载了面板的对象");
return;
}
if (!Directory.Exists(Application.dataPath + "/Scripts/UI"))
{
Directory.CreateDirectory(Application.dataPath + "/Scripts/UI");
}
string className = panelObject.name;
string path = Application.dataPath + $"/Scripts/UI/{className}.cs";
WriteClass(path, className);
WriteAllMembers(panelObject, path);
WriteClassEnd(path);
isCreated = true;
EditorPrefs.SetBool("isCreated", true);
AssetDatabase.Refresh();
}
[DidReloadScripts()]
private static void AssignUI()
{
isCreated = EditorPrefs.GetBool("isCreated");
if (isCreated)
{
GameObject[] gameObjects = Selection.gameObjects;
if (gameObjects.Length == 0)
{
EditorPrefs.SetBool("isCreated", false);
Debug.LogError("你没有选中一个对象");
return;
}
GameObject panelObject = gameObjects[0];
Assembly assembly = Assembly.GetAssembly(typeof(BasePanel));
Type panelType = assembly.GetType(panelObject.name);
if (panelType == null)
{
EditorPrefs.SetBool("isCreated", false);
Debug.LogError("不存在对应名字的面板");
return;
}
Component panel = panelObject.AddComponent(panelType);
FieldInfo[] fieldInfos = panelType.GetFields();
Button[] buttons = panelObject.GetComponentsInChildren<Button>();
Slider[] sliders = panelObject.GetComponentsInChildren<Slider>();
Toggle[] toggles = panelObject.GetComponentsInChildren<Toggle>();
InputField[] inputFields = panelObject.GetComponentsInChildren<InputField>();
Dropdown[] dropdowns = panelObject.GetComponentsInChildren<Dropdown>();
ScrollRect[] scrollRects = panelObject.GetComponentsInChildren<ScrollRect>();
foreach (FieldInfo fieldInfo in fieldInfos)
{
Type type = fieldInfo.FieldType;
if (fieldInfo.FieldType == typeof(Button))
{
for (int i = 0; i < buttons.Length; i++)
{
if (buttons[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, buttons[i]);
}
}
}
if (fieldInfo.FieldType == typeof(Toggle))
{
for (int i = 0;i < toggles.Length; i++)
{
if (toggles[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, toggles[i]);
}
}
}
if (fieldInfo.FieldType == typeof(Slider))
{
for (int i = 0; i < sliders.Length; i++)
{
if (sliders[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, sliders[i]);
}
}
}
if (fieldInfo.FieldType == typeof(InputField))
{
for (int i = 0; i < inputFields.Length; i++)
{
if (inputFields[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, inputFields[i]);
}
}
}
if (fieldInfo.FieldType == typeof(Dropdown))
{
for (int i = 0; i < dropdowns.Length; i++)
{
if (dropdowns[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, dropdowns[i]);
}
}
}
if (fieldInfo.FieldType == typeof(ScrollRect))
{
for (int i = 0; i < scrollRects.Length; i++)
{
if (scrollRects[i].name == fieldInfo.Name)
{
fieldInfo.SetValue(panel, scrollRects[i]);
}
}
}
}
EditorPrefs.SetBool("isCreated", false);
}
}
private static void WriteClass(string path, string className)
{
File.WriteAllText(path, "using UnityEngine;\nusing UnityEngine.UI;\n\n" +
$"public class {className} : BasePanel\n{{\n");
}
private static void WriteMember(string path, string name, Type type, string accessModifier = "public")
{
File.AppendAllText(path, $" {accessModifier} {type.Name} {name};\n");
}
private static void WriteClassEnd(string path)
{
File.AppendAllText(path, "}\n");
}
private static void WriteMethod(string path, string methodName, string accessModifier, bool isoverride = false,string type = "void")
{
if(isoverride)
{
File.AppendAllText(path, $"\n {accessModifier} override {type} {methodName}()\n" +" {");
}
else
{
File.AppendAllText(path, $"\n {accessModifier} {type} {methodName}()\n" +" {");
}
}
private static void WriteMethodEnd(string path)
{
File.AppendAllText(path, "\n }\n");
}
private static void WriteButton(string path,string buttonName)
{
File.AppendAllText(path, $"\n {buttonName}.onClick.AddListener(() =>\n {{\n\n }});");
}
private static void WriteToggle(string path, string toggleName)
{
File.AppendAllText(path, $"\n {toggleName}.onValueChanged.AddListener((value) =>\n {{\n\n }});");
}
private static void WriteSlider(string path, string silderName)
{
File.AppendAllText(path, $"\n {silderName}.onValueChanged.AddListener((value) =>\n {{\n\n }});");
}
private static void WriteScrollView(string path, string scrollName)
{
File.AppendAllText(path, $"\n {scrollName}.onValueChanged.AddListener((value) =>\n {{\n\n }});");
}
private static void WriteDropDown(string path, string dropDownName)
{
File.AppendAllText(path, $"\n {dropDownName}.onValueChanged.AddListener((value) =>\n {{\n\n }});");
}
private static void WriteInputField(string path, string inputFieldName)
{
File.AppendAllText(path, $"\n {inputFieldName}.onEndEdit.AddListener((value) =>\n {{\n\n }});");
}
private static void WriteAllMembers(GameObject panelObject, string path)
{
Button[] buttons = panelObject.GetComponentsInChildren<Button>();
Slider[] sliders = panelObject.GetComponentsInChildren<Slider>();
Toggle[] toggles = panelObject.GetComponentsInChildren<Toggle>();
InputField[] inputFields = panelObject.GetComponentsInChildren<InputField>();
Dropdown[] dropdowns = panelObject.GetComponentsInChildren<Dropdown>();
ScrollRect[] scrollRects = panelObject.GetComponentsInChildren<ScrollRect>();
foreach (Button button in buttons)
{
WriteMember(path, button.name, typeof(Button));
}
foreach (Slider slider in sliders)
{
WriteMember(path, slider.name, typeof(Slider));
}
foreach (Toggle toggle in toggles)
{
WriteMember(path, toggle.name, typeof(Toggle));
}
foreach (InputField inputField in inputFields)
{
WriteMember(path, inputField.name, typeof(InputField));
}
foreach (Dropdown dropdown in dropdowns)
{
WriteMember(path, dropdown.name, typeof(Dropdown));
}
foreach (ScrollRect scrollRect in scrollRects)
{
WriteMember(path, scrollRect.name, typeof(ScrollRect));
}
WriteMethod(path, "Initialize", "protected", true);
foreach (Button button in buttons)
{
WriteButton(path, button.name);
}
foreach (Slider slider in sliders)
{
WriteSlider(path, slider.name);
}
foreach (Toggle toggle in toggles)
{
WriteToggle(path, toggle.name);
}
foreach (InputField inputField in inputFields)
{
WriteInputField(path, inputField.name);
}
foreach (Dropdown dropdown in dropdowns)
{
WriteDropDown(path, dropdown.name);
}
foreach (ScrollRect scrollRect in scrollRects)
{
WriteScrollView(path, scrollRect.name);
}
WriteMethodEnd(path);
}