Unity工具类扩展——UGUI代码/脚本自动化生成 (一)

【为什么要做自动化工具】

工具类的创建是为了解决实际问题或者优化既有流程,我们来先看看一些项目里面经常遇到的问题。

下面这个工具就是可以直接创建一个功能的基础脚本类,就不用每次去复制上次的代码了。然后再帮我们把那些乱七八糟又数不胜数的按钮、文字、图片组件都自动生成在脚本里面,然后自己去关联好引用,一下就能节省好多重复的活。

效果图

简单的 一层

qqw12.gif

复杂点的 管理Panel 子管理Panel 孙管理

image

代码部分解析

1 枚举类型 UIMarkType 对应指定的类型 UIType是默认自有的类型可以自己拓展

public enum UIMarkType{
DefaultUnityElement = 0,
Element = 1
  }
public enum UIType
{
Transform = 0,
Image = 1,
RawImage = 2,
Button = 3,
Toggle = 4,
Slider = 5,
Scrollbar = 6,
Dropdown = 7,
InputField = 8,
ScrollRect = 9,
Text = 10,
ToggleGroup = 11,
Canvas = 12,
RectTransform = 13,
Animator = 14,
IMark = 15,

}

2 接口IMark 主要用于拓展
public interface IMark
{
string ComponentName { get; }


UIMarkType GetUIMarkType();

}

ComponentName获取要创建的类型字符
GetUIMarkType() 获取当前UIMarkType

3 UIMark标签类 用于标记生成什么样

public class UIMark : MonoBehaviour, IMark
{
[Header("指定类型")]
public UIMarkType MarkType = UIMarkType.DefaultUnityElement;
[Header("当前选择创建属性类型")]
public UIType CreateType;


[Header("创建脚本类名")]
public string CustomComponentName;

public UIMarkType GetUIMarkType()
{
    return MarkType;
}

public virtual string ComponentName
{
    get
    {

        if (MarkType == UIMarkType.DefaultUnityElement)
        {
            if (CreateType == UIType.IMark)
            {
                return GetComponents<IMark>().First(v=>v.GetType()!=this.GetType()).ComponentName;
            }
            return CreateType.ToString();
        }


        return CustomComponentName;
    }
}

public void InitCreateType()
{
    if (MarkType == UIMarkType.DefaultUnityElement)
    {

        var TempMark = GetComponents<IMark>().Where(v => v.GetType() != this.GetType());

        if (TempMark.Count()>0)
            CreateType = UIType.IMark;

       else  if (null != GetComponent<ScrollRect>())
            CreateType = UIType.ScrollRect;
        else if (null != GetComponent<InputField>())
            CreateType = UIType.InputField;
        else if (null != GetComponent<Text>())
            CreateType = UIType.Text;
        else if (null != GetComponent<Button>())
            CreateType = UIType.Button;
        else if (null != GetComponent<RawImage>())
            CreateType = UIType.RawImage;
        else if (null != GetComponent<Toggle>())
            CreateType = UIType.Toggle;
        else if (null != GetComponent<Slider>())
            CreateType = UIType.Slider;
        else if (null != GetComponent<Scrollbar>())
            CreateType = UIType.Scrollbar;
        else if (null != GetComponent<Image>())
            CreateType = UIType.Image;
        else if (null != GetComponent<ToggleGroup>())
            CreateType = UIType.ToggleGroup;

        else if (null != GetComponent<Animator>())
            CreateType = UIType.Animator;

        else if (null != GetComponent<Canvas>())
            CreateType = UIType.Canvas;

        else if (null != GetComponent<RectTransform>())
            CreateType = UIType.RectTransform;

        else if (null != GetComponent<Transform>())
            CreateType = UIType.Transform;

    }
}

}

实现了了IMark
[Header("xxx")]  在Inspector面板上给定义的字段的上一行加段描述
InitCreateType()是用来识别当前适合什么自有的类型 如果太多组件可能会错就要Inspector面板改了

4 CustomEditorUIMarkEditor类 用于UIMark类的自定义Inspector面板

[CanEditMultipleObjects, CustomEditor(typeof(UIMark))]
public class CustomEditorUIMarkEditor : Editor
{
public override void OnInspectorGUI()
{

    EditorGUILayout.PropertyField(this.serializedObject.FindProperty("MarkType"));

    if (this.serializedObject.FindProperty("MarkType").enumValueIndex == 1)
    {
        EditorGUILayout.PropertyField(this.serializedObject.FindProperty("CustomComponentName"));
    }
    else
    {
        EditorGUILayout.PropertyField(this.serializedObject.FindProperty("CreateType"));
    }


    // 应用属性修改
    this.serializedObject.ApplyModifiedProperties();
}

}

EditorGUILayout.PropertyField 搜索自定义的类里面的属性名称 然后绘制
特性[CanEditMultipleObjects, CustomEditor(typeof(UIMark))] 每个需要重新自定义面板都需要打上这个特性标签

效果大概这样


image.png

image.png

5 AddUIMark类 右键添加按钮UIMark的

public class AddUIMark
{
[MenuItem("GameObject/KGUI/AddUIMark", priority = 0)]
static void AddUIMarkMenu()
    {
    GameObject[] obj = Selection.gameObjects;

    for (int i = 0; i < obj.Length; i++)
    {
        
        if (!obj[i].GetComponent<UIMark>())
        {
            obj[i].AddComponent<UIMark>().InitCreateType();
        }
        else
        {
            obj[i].GetComponent<UIMark>().InitCreateType();
        }

    }
}
}

MenuItem 按钮的定义 想要在Hierarchy视图右键的话 路径就要GameObject/下的 然后要选层级 默认层级是不出现在右键的

6 GeneratorData 就一些静态数据

public class GeneratorData
{
#region UIClass

public static string UIClass =
   @"using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
public class #类名# : MonoBehaviour
{

//替换标签

#region UIModule

#成员#

#endregion

 public void Awake()
{
    InitFind();
}

 public void InitFind()
{
#查找#
}

}
";
    #endregion
#region ElementClass

  public static string ElementClass =
 @"using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
public class #类名# : MonoBehaviour
{
//这是子类
//替换标签

#region UIModule

#成员#

#endregion

 public void Awake()
{
    InitFind();
}

 public void InitFind()
{
#查找#
}

}
";
    #endregion
    public static Type GetType(string name)
    {
   // Type type=null;
    var AssemblyCSharp = AppDomain.CurrentDomain.GetAssemblies().First(v =>     v.FullName.StartsWith("Assembly-CSharp,"));
        return AssemblyCSharp.GetType(name);
    }
}

var AssemblyCSharp 是获取所有程序集筛选Assembly-CSharp 这个集

7 UICodeGenerator 一键生成添加脚本

public class UICodeGenerator
{
    private static Action ff;
    public static GameObject gg;
    public static string tt="fff";
    [MenuItem("GameObject/KGUI/生成脚本", priority = 0)]
    public static void UIScriptGenerator()
    {

         if (EditorPrefs.GetBool("ScriptGenerator"))
        {
              return;
        }
    GameObject[] selectobjs = Selection.gameObjects;

    foreach (GameObject go in selectobjs)
    {
        Generator(go);        
    }
}


public static void ScriptGenerator(GameObject go,string UIClass, string Classname="")
{
    //选择的物体
    GameObject selectobj = go;

    //物体的子物体
    List<Transform> childList = selectobj.GetComponentsInChildren<Transform>(true).ToList();
    Debug.Log(childList);
    List<Transform> ElementList = childList.Where(v => { return v.GetComponent<UIMark>() && v.GetComponent<UIMark>().MarkType == UIMarkType.Element&&v!= go.transform; }).ToList();

    ElementList.ForEach(v =>
    {
        v.GetComponentsInChildren<Transform>(true).Where(Obj => {return Obj.GetComponent<UIMark>()&& Obj != v;}).ToList().ForEach(remove =>
            {
                childList.Remove(remove);
            });
    });
    if (childList.Contains(go.transform))
    {
        childList.Remove(go.transform);
    }
    //  List<Transform> childList = new List<Transform>(_transforms);

    //UI需要查询的物体
    var mainNode = childList.Where(v => v.GetComponent<UIMark>());

    var nodePathList = new Dictionary<string, string>();

    string ClassName = Classname == "" ? go.name : Classname;

    //循环得到物体路径
    foreach (Transform node in mainNode)
    {
        Transform tempNode = node;
        string nodePath = "/" + tempNode.name;

        while (tempNode != go.transform)
        {
            tempNode = tempNode.parent;

            if (tempNode != go.transform)
            {
                int index = nodePath.IndexOf('/');

                nodePath = nodePath.Insert(index, "/" + tempNode.name);
            }
          
        }
        nodePath = nodePath.Substring(1);
        nodePathList.Add(node.name, nodePath);
    }

    //成员变量字符串
    string memberstring = "";
    //查询代码字符串
    string loadedcontant = "";

    foreach (Transform itemtran in mainNode)
    {
        //每个类的名字 字符
        string typeStr = itemtran.GetComponent<UIMark>().ComponentName;

        // Debug.Log();
        memberstring += "public " + typeStr + " " + itemtran.gameObject.name + " = null;\r\n\t";
        //物体的路劲寻找 字符
        loadedcontant += "\t\t" + itemtran.name + " = " + "gameObject.transform.Find(\"" + nodePathList[itemtran.name] + "\").GetComponent<" + typeStr + ">();\r\n";
    }


    string scriptPath = Application.dataPath + "/Scripts/" + ClassName + ".cs";


    string classStr = "";

    gg = selectobj;
    tt = selectobj.name;

    if (!Directory.Exists(Application.dataPath + "/Scripts"))
    {
        Directory.CreateDirectory(Application.dataPath + "/Scripts");
    }
    if (File.Exists(scriptPath))
    {
        FileStream classfile = new FileStream(scriptPath, FileMode.Open);
        StreamReader read = new StreamReader(classfile);
        classStr = read.ReadToEnd();
        read.Close();
        classfile.Close();
        File.Delete(scriptPath);

        //分割 区分手写和 生成的
        string splitStr = "//替换标签";
        string unchangeStr = Regex.Split(classStr, splitStr)[0];
        string changeStr = Regex.Split(GeneratorData.UIClass, splitStr)[1];

        StringBuilder build = new StringBuilder();
        build.Append(unchangeStr);
        build.Append(splitStr);
        build.Append(changeStr);
        classStr = build.ToString();
    }
    else
    {
        classStr =UIClass;
    }

    classStr = classStr.Replace("#类名#", ClassName);
    classStr = classStr.Replace("#查找#", loadedcontant);
    classStr = classStr.Replace("#成员#", memberstring);


    FileStream file = new FileStream(scriptPath, FileMode.CreateNew);

    StreamWriter fileW = new StreamWriter(file, System.Text.Encoding.UTF8);
    fileW.Write(classStr);
    fileW.Flush();
    fileW.Close();
    file.Close();


    Debug.Log("创建脚本 " + Application.dataPath + "/Scripts/" + ClassName + ".cs 成功!");
}

public static void Generator(GameObject go)
{

    ScriptGenerator(go, GeneratorData.UIClass);



    go.GetComponentsInChildren<UIMark>(true).Where(v=>v.MarkType==UIMarkType.Element).ToList().ForEach(v=> 
    {

        ScriptGenerator(v.gameObject, GeneratorData.ElementClass,v.CustomComponentName);
    });

    EditorPrefs.SetBool("ScriptGenerator", true);

    AssetDatabase.SaveAssets();
    AssetDatabase.Refresh();

    

}

[UnityEditor.Callbacks.DidReloadScripts]
public static void AddScript()
{
    if (!EditorPrefs.GetBool("ScriptGenerator"))
    {
        return;          
    }
    EditorPrefs.SetBool("ScriptGenerator", false);
    AssetDatabase.Refresh();




    Selection.gameObjects.ToList().ForEach(v =>
    {


        if (!v.GetComponent(GeneratorData.GetType(v.name)))
            v.AddComponent(GeneratorData.GetType(v.name));


        v.GetComponentsInChildren<UIMark>(true).Where(element => element.MarkType == UIMarkType.Element).ToList().ForEach(elementMark =>
        {
            if (!elementMark.GetComponent(GeneratorData.GetType(elementMark.CustomComponentName)))
            {
                elementMark.gameObject.AddComponent(GeneratorData.GetType(elementMark.CustomComponentName));
                UnityEngine.Object.DestroyImmediate(elementMark);


            }

        });

    });

    Debug.Log(tt+12344);
  
}

}

EditorPrefs.Set/GetBool 用于面板存取数据的

UIScriptGenerator()会遍历当前选择的物体进行生成脚本
Generator() 处理生成脚本的逻辑

ScriptGenerator() 指定物体为他生成相应的脚本

先筛选出符合条件的属性的 mainNode
循环得到 物体的路径 生成路径字符
判断是否含有该文件夹没有则创建

 if (!Directory.Exists(Application.dataPath + "/Scripts"))
        {
            Directory.CreateDirectory(Application.dataPath + "/Scripts");
        }

通过File.Exists判断是否有该脚本 有的就只是修改脚本没有就创建

AddScript() 代码生成后 的添加操作

特性[UnityEditor.Callbacks.DidReloadScripts]用于脚本改动的回调

好了 以上就是 整个过程


工程地址 https://github.com/LKaiGuo/KGScriptGenerator 喜欢给个星星

u3d萌新QQ群844087555 欢迎进来灌水=。=

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351