unity编辑器拓展之自动生成脚本模板

    项目开发过程中,UI面板有许多,关于UI面板上面按钮,文本是应该声明public直接拖拽赋值还是应该定义成private一层层去find,其中利弊各有说法,以前有个老大说是find会影响运行速度,但是现在的老大又不让直接拖拽赋值,说实话这些东西都无所谓,用哪种方式主要看老大的习惯,咱们就不去分析其中利弊了,但是如果定义成private去find,那么多控件,find起来还是比较烦,所以只好写一个工具类去直接生成了。

    其实这个工具是现在的老大有一天突然发出来让用的,但是是NGUI用的,博主最近Demo用的UGUI,就拿来改写了一下,所以严格上说不是博主自创,只是借鉴然后略微改进了一些而已。原理上就是遍历子物体,然后将需要定义的对象如文本,按钮,图片定义一下,然后加入find代码,按钮的话再加个点击事件,把这些内容写入一个文本就行。

    先说一下具体的原理吧。就是根据预设某个子对象的名字,判断该对象是什么类型(text or img or btn),也就是说在创建预设的时候名字不能随便起,例如所有要定义在脚本中的文本都要以Txt结尾(或者名字中包含Txt),之后在遍历到这个对象时就可以确定这个对象的类型,然后就可以根据对象类型做不同处理,比如find时候的getcomponment,以及按钮的添加事件。然后还有..好像也没什么了我觉得。总之名字一定要起好,不能重名这样子,其他的也没什么了。

    因为这个工具有两个功能,一个是create,一个是fix,而且是混合到一块的,所以就有些地方的参数就有点多,这个就暂时不用管,后面的话会有源代码。

static Dictionary<string, string> childrenNames;//对象子物体列表
    static Dictionary<string, string> Rules = new Dictionary<string, string>()
    {
        {"Txt","Text" }, {"Btn","Button" }, { "Img","Image"}
    };//命名规则
    static Transform SelTran;//选择的对象
    static List<string> names;//重名的子物体
    static List<string> btnNames;//所有按钮
    TextAsset txt;//Fix选择的脚本
    string input;//Fix规则


    static Dictionary<string, string> FixChilderNames;//fix新加的子物体
    static List<string> FixBtnNames;//fix新加的按钮

    字典的key是命名格式,value是对应的对象类型。这个是UGUI的,NGUI的就是{“Txt”,“Label”}。字典就按照自己的命名习惯来好了。注意value别写错了。

    const string FixDef = "//FixStartDefiened";
    const string FixFind = "//FixStartFind";
    const string FixAddEvent = "//FixStartAddEvent";
    const string FixEvent = "//FixStartEvent";

    这几个是后面用到的,相当于于一个记号吧,后面会解释,暂时不用理会。

    private void OnGUI()
    {
        txt = EditorGUILayout.ObjectField("drag cs", txt, typeof(TextAsset), true) as TextAsset;
        EditorGUILayout.Space();

        SelTran = EditorGUILayout.ObjectField("Drag transform", SelTran, typeof(Transform), true) as Transform;
        EditorGUILayout.Space();

        input = EditorGUILayout.TextField("输入结尾符,fix脚本时必填:",input);
        EditorGUILayout.Space();

        if (GUILayout.Button("Create", GUILayout.Width(200)))
        {
            Create();
        }

        EditorGUILayout.Space();

        if (GUILayout.Button("Fix", GUILayout.Width(200)))
        {
            FixScripts();
        }

    }

    拓展窗口,ongui绘制界面,各个参数上面都有注释。先说下create方法,创建脚本。

    void Create()
    {
        childrenNames = new Dictionary<string, string>();
        names = new List<string>();
        btnNames = new List<string>();
        GetChildren(SelTran);
        CreateScript(Application.dataPath + "/" + SelTran.name + ".cs", DealScript().ToString());

    }

    首先实例化字典,列表。然后GetChilder方法是获取所有的脚本中要定义的所有对象名称,CreateScript只是IO流的写入。

void GetChildren(Transform tran, string name = "",string rule = "")
    {
        if (string.IsNullOrEmpty(name)) name = tran.name;
        int childNum = tran.childCount;
        if(string.IsNullOrEmpty(rule))
        {
            string value = AddName(tran.name);
            if (!string.IsNullOrEmpty(value)) childrenNames.Add(name, value);//符合规则,加入列表
        }
        else//fix
        {
            string valuefix = AddName(tran.name, rule);
            if (!string.IsNullOrEmpty(valuefix)) FixChilderNames.Add(name, valuefix);
        }

        if (childNum == 0)
        {
            return;
        }
        else
        {
            string temp = name;
            for (int i = 0; i < childNum; i++)
            {
                temp = name;
                temp = temp + "/" + tran.GetChild(i).name;
                GetChildren(tran.GetChild(i), temp,rule);
            }
        }
    }
     string AddName(string name,string rule="")
    {
        if (names.Contains(name))
        {
            Debug.LogError(name + "重名!!!!");
        }
        foreach (KeyValuePair<string, string> item in Rules)
        {
            if (name.Contains(item.Key))
            {
                if (string.IsNullOrEmpty(rule))//非fix
                {
                    return item.Value;
                }
                else
                {
                    if (name.EndsWith(rule)) return item.Value;
                }
            }
        }
        return string.Empty;
    }

    GetChilder方法第一个参数是要遍历的对象,第二个参数是路径,也就是find时候的路径,是需要一个对象一个对象连起来的,第三个参数暂时用不到。然后逻辑就这个样子,用递归的方法去遍历,然后符合规则的写入到字典中。Addname方法是判断是不是符合规则,符合规则则返回当前名字对应的类型,不符合的返回空,不写入字典。递归这个东西理解了就是理解了,理解不了就钻牛角尖出不来了,不过有个技巧,就是理解递归的时候不要凭空的去想,看着一个预设,然后假设将这个预设套入到这个方法里面运行结果是什么,是不是符合要求。

    获取到所有的要定义的对象名字及类型,之后就是写入文件中了,这个代码有点多,就不粘贴了,比较简单,列举一部分,定义stringbuilder,然后用stringbuild的append,appenline,appendformat方法去规定脚本格局,例如下面代码是引用及定义部分的。

StringBuilder sb = new StringBuilder();
        sb.AppendLine("using UnityEngine;");
        sb.AppendLine("using UnityEngine.UI;");
        sb.AppendLine("using System.Text;");
        sb.AppendLine("using XLua;");
        sb.AppendLine();
        sb.AppendLine();

        sb.AppendFormat("public class {0} : MonoBehaviour ", SelTran.name);
        sb.AppendLine();
        sb.AppendLine("{");

        var dic = childrenNames.GetEnumerator();
        while (dic.MoveNext())
        {
            string[] names = dic.Current.Key.Split('/');
            string name = names[names.Length - 1];
            string value = dic.Current.Value;
            sb.AppendFormat("\tprivate {0} {1};", value, name);
            sb.AppendLine();

        }
        sb.AppendLine(FixDef);

    是不是就很简单,就是平时写代码那样..注意appendformat的第一个参数string,不要以“{}”结尾,这样的话应该会有识别问题,不是很确定,有兴趣的可以试一下。定义部分就这样子,然后下面find,以及按钮的事件添加,就类似,主要还是看自己的项目中的写法是怎么样的,注意写find的时候不要写给按钮的点击事件的代码,这样的比较乱,比较好的做法就是在写find的时候顺便将按钮筛选出来,然后find之后再写按钮的代码,这样子就好很多。代码全部写完之后就直接IO写入就行了,目前博主文件直接写在assets了,后面读者自己优化路径吧。


    第一个是博主的预设,第二个图是拓展窗口,将预设拖到拓展窗口的Trannstorm栏,点击create之后,看到“ok”log之后就可以看到生成的脚本,如下


    是不是很方便,路径也没问题。到此,create就算完成了,具体的内容还是根据自己的项目来定,比如引用命名空间,按钮事件定义。这些也是根据我们老大的代码改写的UGUI版本,但是有个缺点就是假如说预设中新加了一些对象需要定义的,但是这个脚本代码已经写了很多,所以不可能删了重新创建,然后就还是需要手写find,还是挺麻烦的,所以楼主寻思的实现一下在不改变原有代码的情况下,新增对象定义,然后算是找到了还算可以的解决方法。

    博主的思路是在创建脚本的时候,留下标志,作为拓展用,比如在find代码块结尾加入一个标志,作为拓展的入口。博主一共加了四个标志,也就是最上面定义的四个常量,分别是对象定义结尾,对象find结尾,按钮添加事件结尾,以及按钮事件结尾。然后博主只要找到新加入的对象,就可以直接在这些结尾的地方写入相应的代码,这样就不会影响原有的代码了。接下来是如何区分原有的和新加的对象,博主的方法是在名字结尾加入标志符号,以此来作为区分,也就是上面定义的rule,虽然很low,但是还可以用,抛砖引玉吧。然后先将新加的对象的定义,find等代码写好,直接替换掉原来定义的入口就行,不过记得在下面记得还要加入入口,以便下次修改。代码如下 

    void FixScripts()
    {
        FixChilderNames = new Dictionary<string, string>();
        FixBtnNames = new List<string>();
        names = new List<string>();
        string path = AssetDatabase.GetAssetPath(txt);//获取选定脚本的路径
        Debug.Log(path);
        if (string.IsNullOrEmpty(input))
        {
            Debug.LogError("Error!!!");
            return;
        }
        GetChildren(SelTran,string.Empty,input);

        StreamReader sr = new StreamReader(path, Encoding.UTF8);
        string msg = sr.ReadToEnd();
        FixDealScript(ref msg);
        sr.Close();
        //FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Write);
        //StreamWriter sw = new StreamWriter(fs);
        StreamWriter sw = new StreamWriter(path,false,Encoding.UTF8);
        sw.Write(msg);
        sw.Close();
        AssetDatabase.Refresh();
        Debug.Log("fix ok------");
    }

    首先要获取文本路径,然后用于写入,但是记得rule不能为空,不然就报错了,这些非法检测博主没有写,然后还是利用上面的 GetChildren方法获取到所有的新增的对象名。之后就是读取脚本内容,处理脚本字符串了。处理过程也比较简单,就是先写新增对象的代码,然后替换入口就好,这个后面就后面自己看吧。

    博主在修改了刚才了预设,新增的以“1”结尾,同时修改代码,然后打开窗口,将预设拖入transform栏,将脚本拖入TextAsset栏,规则栏输入1,点击fix。看到“fix ok”log,效果如下。


    新加的代码privite string name 没有影响到,新增的对象有全部加入,就布局问题,可以不用纠结,直接快捷键自动对齐就行,如果真的纠结的话就在写脚本的时候规划下吧,博主就不调了,直接快捷键解决。如果下次又有新增的,就换一个标识,例如“2”,再新增就继续换,再新增的话你就可以找策划谈话了..虽然这样标识很low.但是.博主没想到其他什么办法,而且,只是一个工具类,主要是节约时间,方便写代码,觉得不用纠结太多。

    好了,本篇到此结束,需要优化的地方还有很多,比如创建脚本的路径,比如非法操作的判断等等,有不懂的或者有bug可以私信,或者直接改..博主觉得也没什么难度...下面代码原文件链接。

    链接:https://pan.baidu.com/s/1JCsXSgZYKMzzwEM2Eo08Rg 密码:x02w


猜你喜欢

转载自blog.csdn.net/a598211757/article/details/80032238