12.脚本动态添加到面板

12.脚本动态添加到面板


12.1 知识点

要等非编辑器程序集编译完再把脚本添加到面板对象上,所以在回调中处理

CompilationPipeline.assemblyCompilationFinished += CompilationPipeline_assemblyCompilationFinished;

回调中判断非编辑器的程序集编译完成,目的是获取Assembly-CSharp程序集.反射拼接路径,路径截取Asset前的路径加上拼接后的程序集路径,给选中对象添加脚本

private void CompilationPipeline_assemblyCompilationFinished(string arg1, CompilerMessage[] arg2)
{
    // 非编辑器的程序集编译完成
    if(arg1.Contains("Assembly-CSharp.dll"))
    {
        // 这样无法添加成功
        // Selection.activeGameObject.AddComponent(Type.GetType(panelName));
        // 我们需要用到一些反射的知识,读取程序集,通过程序集去获取对应类型的组件
        System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets")) + arg1);
        Selection.activeGameObject.AddComponent(assembly.GetType(panelName));
        // 事件 有加就有减
        CompilationPipeline.assemblyCompilationFinished -= CompilationPipeline_assemblyCompilationFinished;
    }
}

12.2 知识点代码

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class UIPanelTool : EditorWindow
{
    private string panelBaseScript;
    private string panelScript;

    private string panelName;

    //GUI控件显示用
    private Vector2 nowPos;
    private Vector2 nowPos2;

    private List<string> defaultName = new List<string>() { "Image",
                                                            "Text (TMP)",
                                                            "RawImage",
                                                            "Background",
                                                            "Checkmark",
                                                            "Label",
                                                            "Text (Legacy)",
                                                            "Arrow",
                                                            "Placeholder",
                                                            "Fill",
                                                            "Handle",
                                                            "Viewport",
                                                            "Scrollbar Horizontal",
                                                            "Scrollbar Vertical"};

    //用于记录 对应控件类型的字典
    private Dictionary<string, Type> controlType = new Dictionary<string, Type>();

    [MenuItem("GameObject/UI/自动生成面板脚本文件")]
    private static void CreateToolPanel()
    {
        UIPanelTool win = EditorWindow.GetWindow<UIPanelTool>("自动生成面板脚本工具");
        win.Show();
        //对面板上显示的相关信息进行初始化
        //需要自动生成的代码 在这里 就应该拼接好
        win.InitInfo();
    }

    public void InitInfo()
    {
        panelBaseScript = "";
        panelScript = "";
        
        //1.获取选择的对象
        GameObject obj = Selection.activeGameObject;
        if (obj == null)
            return;
        //获取面板名
        panelName = obj.name;
        //获取所有的按钮控件
        ControlStrInfo strInfo = new ControlStrInfo();
        //找按钮
        ControlStrInfo controlInfo = FindControl<Button>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<Toggle>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<Slider>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<InputField>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<Dropdown>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<ScrollRect>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<Text>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<TextMeshProUGUI>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<Image>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        controlInfo = FindControl<RawImage>(obj);
        //如果为null 证明控件查找有问题 存在同名有用 控件
        if (controlInfo == null)
            return;
        strInfo += controlInfo;

        //已经获取到了对应的脚本代码 需要拼接进模板当中
        TextAsset baseStr = AssetDatabase.LoadAssetAtPath<TextAsset>("Assets/Editor/UIPanelTool/UIConfigBase.txt");
        panelBaseScript = string.Format( baseStr.text, //模板文件中的字符串信息
                                                obj.name,//{0} 面板名
                                                strInfo.nameStr,//{1} 控件变量的声明
                                                strInfo.findStr, //{2} 控件的查找
                                                strInfo.listenerStr, //{3} 控件事件的监听
                                                strInfo.funcStr);//{4} 控件事件对应的响应函数 虚函数

        TextAsset str = AssetDatabase.LoadAssetAtPath<TextAsset>("Assets/Editor/UIPanelTool/UIConfig.txt");
        panelScript = string.Format(str.text, obj.name, obj.name);
    }

    private ControlStrInfo FindControl<T>(GameObject obj) where T:UIBehaviour
    {
        ControlStrInfo info = new ControlStrInfo();
        T[] controls = obj.GetComponentsInChildren<T>();
        Type type = typeof(T);
        for (int i = 0; i < controls.Length; i++)
        {
            //通过判断该控件名字是否是不需要进行代码生成的 如果是 就直接跳过
            //1.不用的控件,不记录
            if( defaultName.Contains(controls[i].name) ||
                controls[i].name == obj.name)
                continue;

            if(controlType.ContainsKey(controls[i].name))
            {
                //2.重复名字的相同控件判断
                if (controlType[controls[i].name] == type)
                {
                    EditorUtility.DisplayDialog("重复控件名", $"有两个控件类型相同的对象重名了{controls[i].name}", "确定");
                    return null;
                }
                //3.同名对象,不同类型的控件 比如 Button和Image会出现在一个对象上
                //  只需要保证重要的控件已经生成代码了就可以了 相对不重要的控件 直接忽略不记录
                else
                    continue;
            }

            controlType.Add(controls[i].name, type);

            //声明相关的拼接
            info.nameStr += $"public {type.Name} {controls[i].gameObject.name};\n\t";
            info.findStr += $"{controls[i].gameObject.name} = this.transform.Find(\"{GetPath(controls[i].transform, obj.transform)}\").GetComponent<{type.Name}>();\n\t\t";

            switch (type.Name)
            {
                case "Button":
                    info.listenerStr += $"{controls[i].gameObject.name}.onClick.AddListener(On{controls[i].gameObject.name}Click);\n\t\t";
                    info.funcStr += $"protected virtual void On{controls[i].gameObject.name}Click(){{}}\n\t";
                    break;
                case "Toggle":
                    info.listenerStr += $"{controls[i].gameObject.name}.onValueChanged.AddListener(On{controls[i].gameObject.name}ValueChanged);\n\t\t";
                    info.funcStr += $"protected virtual void On{controls[i].gameObject.name}ValueChanged(bool value){{}}\n\t";
                    break;
                case "Slider":
                    info.listenerStr += $"{controls[i].gameObject.name}.onValueChanged.AddListener(On{controls[i].gameObject.name}ValueChanged);\n\t\t";
                    info.funcStr += $"protected virtual void On{controls[i].gameObject.name}ValueChanged(float value){{}}\n\t";
                    break;
                default:
                    break;
            }
        }

        return info;
    }

    /// <summary>
    /// 获取对象的父对象关系,拼接 路径 用于获取控件
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    private string GetPath(Transform obj, Transform panelTrans)
    {
        string path = obj.name;
        while (obj.parent != panelTrans)
        {
            path = obj.parent.name + "/" + path;
            obj = obj.parent;
        }
        return path;
    }


    private void OnGUI()
    {
        if (panelBaseScript != "")
        {
            nowPos = EditorGUILayout.BeginScrollView(nowPos);
            GUILayout.Label(panelBaseScript);
            EditorGUILayout.EndScrollView();
            GUILayout.Label("-----------------------------------");
            nowPos2 = EditorGUILayout.BeginScrollView(nowPos2);
            GUILayout.Label(panelScript);
            EditorGUILayout.EndScrollView();

            if (GUILayout.Button("选择保存路径"))
            {
                string path = EditorUtility.SaveFilePanel("脚本保存路径", Application.dataPath + "/Scripts/", panelName + "Base", "cs");
                if (path != "")
                {
                    //脚本存储相关的逻辑
                    File.WriteAllText(path, panelBaseScript);
                    //path = path.Replace("Base", "");
                    int index = path.LastIndexOf("Base");
                    path = path.Substring(0, index) + ".cs";
                    //如果已经存在子类脚本 就不要再去覆盖生成了 避免把我们已经写好的 面板相关的逻辑覆盖掉
                    //如果基类当中对控件等内容进行了修改 我们只需要在子类当中根据需求手动修改即可
                    if (!File.Exists(path))
                        File.WriteAllText(path, panelScript);

                    CompilationPipeline.assemblyCompilationFinished += CompilationPipeline_assemblyCompilationFinished;

                    //刷新文件系统
                    AssetDatabase.Refresh();
                }
            }
        }
        else
            GUILayout.Label("存在同名同类型控件,请先解决该问题");
    }

    private void CompilationPipeline_assemblyCompilationFinished(string arg1, CompilerMessage[] arg2)
    {
        //非编辑器的程序集编译完成
        if(arg1.Contains("Assembly-CSharp.dll"))
        {
            //这样无法添加成功
            //Selection.activeGameObject.AddComponent(Type.GetType(panelName));
            //我们需要用到一些反射的知识 读取程序集 通过程序集去获取对应类型的组件
            System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets")) + arg1);
            Selection.activeGameObject.AddComponent(assembly.GetType(panelName));
            //事件 有加就有减
            CompilationPipeline.assemblyCompilationFinished -= CompilationPipeline_assemblyCompilationFinished;
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏