Bootstrap

Unity 编辑器-创建模板脚本,并自动绑定属性,添加点击事件

需求

根据选中的Prefab创建模板脚本

分析

先拆解一下需求,如下

需求
获取Prefab需要挂Inspector的属性
根据获取的属性列表创建模板脚本
添加脚本并自动挂载节点

当使用框架开发时,Prefab挂载的很多脚本都有固定的格式。从Unity的基础模板创建cs文件,再修改到应有的模板,会浪费一些时间。尤其是有大量的不同界面时,每个都改一遍,浪费时间不说,还有可能遗漏或错改。写个脚本创建指定的模板代替C#基础模板。

注:当前脚本使用的NGUI,使用UGUI时替换对应的Component即可

代码

获取属性

遍历所有节点并缓存所有m_开头的节点,标记为需要在脚本中使用的,供后面使用

  	private static List<GameObject> _nodeList;
    private static List<UISprite> _imgList;
    private static List<UIButton> _btnList;
    private static List<UILabel> _labelList;
    private static List<UISlider> _sliderList;
  	private static void GetNodeData(GameObject prefab)
    {
        Transform[] nodes = prefab.GetComponentsInChildren<Transform>(true);
        _nodeList = new List<GameObject>();
        _imgList = new List<UISprite>();
        _labelList = new List<UILabel>();
        _btnList = new List<UIButton>();
        _sliderList = new List<UISlider>();                                                                                              
        // 遍历所有子对象
        foreach (var node in nodes)
        {
            if (!node.gameObject.name.StartsWith("m_")) continue;
            var btn = node.GetComponent<UIButton>();
            var img = node.GetComponent<UISprite>();
            var label = node.GetComponent<UILabel>();
            var slider = node.GetComponent<UISlider>();
            if (btn != null)
            {
                _btnList.Add(btn);
            }
            else if (slider != null)
            {
                _sliderList.Add(slider);
            }
            else if (img != null)
            {
                _imgList.Add(img);
            }
            else if (label != null)
            {
                _labelList.Add(label);
            }
            else
            {
                _nodeList.Add(node.gameObject);
            }
        }
    }
生成模板脚本

生成模板脚本,并在脚本中添加对应的属性,属性名为节点名后半部分

 private const string MenuItemText1 = "Assets/Create/模板脚本/创建Mediator模板脚本";
  
    [MenuItem(MenuItemText1, false, 25)]
    public static void CreateScript()
    {
        CreateScript(false);
    }
 

    private static void CreateScript(bool isPartial)
    {

        var assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
        var directoryPath = System.IO.Path.GetDirectoryName(assetPath);
        var folderName = System.IO.Path.GetFileName(directoryPath);
        var folderPath = $"Assets/Game/GameLogic/{folderName}";//自己对应的路径
        if (!Directory.Exists(folderPath))
        {
            if (directoryPath != null) Directory.CreateDirectory(folderPath);
        }

        var scriptPath = $"{folderPath}/{Selection.activeObject.name}Mediator.cs";
        if (File.Exists(scriptPath))
        {
            Debug.LogError($"The script \"{scriptPath}\" already exists.");
            return;
        }

        var myPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
        // 获取 Prefab 的所有子对象
        GetNodeData(myPrefab);

        var txt = $@"using UnityEngine;
namespace 自己的命名空间
{{
    public {(isPartial ? "partial" : "")} class {Selection.activeObject.name}Mediator : NGuiFormMasterLogic
    {{
    	//自己的模板
        public override string FacadeName
        {{
            get {{ return HomeFacade.Name; }}//TODO:修改FacadeName
        }}

        public new const string NAME = {$"\"{Selection.activeObject.name}Mediator\""};

        public override void OnConstruct(object userData)
        {{
            base.OnConstruct(userData);
            MediatorName = NAME;
        }}
";
        var txtEnd = $@"
    }}
}}";
        for (var i = 0; i < _nodeList.Count; i++)
        {
            var name = _nodeList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private GameObject {name};
";
            txt += str;
        }

        for (var i = 0; i < _imgList.Count; i++)
        {
            var name = _imgList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UISprite {name};
";
            txt += str;
        }

        for (var i = 0; i < _labelList.Count; i++)
        {
            var name = _labelList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UILabel {name};
";
            txt += str;
        }

        for (var i = 0; i < _btnList.Count; i++)
        {
            var name = _btnList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UIButton {name};
";
            txt += str;
        }

        for (var i = 0; i < _sliderList.Count; i++)
        {
            var name = _sliderList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UISlider {name};
";
            txt += str;
        }

        for (var i = 0; i < _btnList.Count; i++)
        {
            var name = _btnList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"


        public void On{name}Click()
        {{
           
        }}

";
            txt += str;
        }


        txt += txtEnd;
        EditorPrefs.SetString("creatorPath", assetPath);
        EditorPrefs.SetString("creatorName", myPrefab.name);
        EditorPrefs.SetString("creatorScriptPath", scriptPath);
        File.WriteAllText(scriptPath, txt);
        AssetDatabase.Refresh();
    }

  
    [MenuItem(MenuItemText1, true)]
    public static bool CreatorNormal()
    {
        if (!Selection.activeObject) return false;
        var prefabAssetType = PrefabUtility.GetPrefabAssetType(Selection.activeObject);
        return prefabAssetType != PrefabAssetType.NotAPrefab;

    }
  


添加脚本并自动挂载节点

根据脚本声明的属性,获取第一个满足条件的节点,并赋值给脚本(对同名节点未作考虑,Prefab 中做规避)

 	private const string MenuItemText3 = "Assets/Create/模板脚本/添加Mediator模板脚本";
   [MenuItem(MenuItemText3, false, 25)]
    public static void AddScript()
    {
        AddComp();
    }
    public static void AddComp()
    {
        var scriptName = EditorPrefs.GetString("creatorName");
        string[] guids = AssetDatabase.FindAssets(scriptName);

        if (guids.Length > 0)
        {
            var assetPath = EditorPrefs.GetString("creatorPath");
            string path = AssetDatabase.GUIDToAssetPath(guids[0]);
            MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
            System.Type type = monoScript.GetClass();
            var prefab =
                GameObject.Instantiate(
                    AssetDatabase
                        .LoadMainAssetAtPath(
                            assetPath)) as GameObject; // PrefabUtility.LoadPrefabContents(assetPath);//
            var component = prefab.AddComponent(type);
            // 获取所有字段
            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            var list = prefab.GetComponentsInChildren<Transform>(true).Where(a => a.name.StartsWith("m_")).ToList();

            foreach (FieldInfo field in fields)
            {
                // 判断字段是否被序列化
                var attributes = field.GetCustomAttributes(typeof(SerializeField), false);
                if (attributes.Length > 0)
                {
                    var temp = field.Name + "";
                    var name = temp[..1].ToLower() + temp[1..];
                    var go = list.FirstOrDefault(a => a.name == $"m_{name}");
                    if (go != null)
                    {
                        list.Remove(go);
                        var btn = go.GetComponent<UIButton>();
                        var slider = go.GetComponent<UISlider>();
                        var img = go.GetComponent<UISprite>();
                        var txt = go.GetComponent<UILabel>();
                        if (btn != null)
                        {
                            var str = $"On{field.Name}Click";
                            Debug.Log(str);
                            var delegateFunc1 = new EventDelegate(component as MonoBehaviour, str);
                            btn.onClick.Add(delegateFunc1);
                            field.SetValue(component, btn);
                        }
                        else if (slider != null)
                        {
                            field.SetValue(component, slider);
                        }
                        else if (img != null)
                        {
                            field.SetValue(component, img);
                        }
                        else if (txt != null)
                        {
                            field.SetValue(component, txt);
                        }
                        else
                        {
                            field.SetValue(component, go.gameObject);
                        }
                    }
                }
            }

            // GetNodeData(prefab);
            // PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, assetPath);
            PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, assetPath, InteractionMode.AutomatedAction);
            Object.DestroyImmediate(prefab);
            AssetDatabase.Refresh();
        }
        else
        {
            Debug.LogError($"请先生成脚本");
        }
    }
    
     [MenuItem(MenuItemText3, true)]
    public static bool AddScript1()
    {
        if (!Selection.activeObject) return false;
        var prefabAssetType = PrefabUtility.GetPrefabAssetType(Selection.activeObject);
        return prefabAssetType != PrefabAssetType.NotAPrefab;

    }

完整脚本

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

public class MediatorScriptCreator
{
    private const string MenuItemText1 = "Assets/Create/模板脚本/创建Mediator模板脚本";
    private const string MenuItemText3 = "Assets/Create/模板脚本/添加Mediator模板脚本";
    [MenuItem(MenuItemText1, false, 25)]
    public static void CreateScript()
    {
        CreateScript(false);
    }
   
    
    [MenuItem(MenuItemText3, false, 25)]
    public static void AddScript()
    {
        AddComp();
    }
    
    private static List<GameObject> _nodeList;
    private static List<UISprite> _imgList;
    private static List<UIButton> _btnList;
    private static List<UILabel> _labelList;
    private static List<UISlider> _sliderList;

    private static void CreateScript(bool isPartial)
    {

        var assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
        var directoryPath = System.IO.Path.GetDirectoryName(assetPath);
        var folderName = System.IO.Path.GetFileName(directoryPath);
        var folderPath = $"Assets/Game/GameLogic/{folderName}";//自己对应的路径
        if (!Directory.Exists(folderPath))
        {
            if (directoryPath != null) Directory.CreateDirectory(folderPath);
        }

        var scriptPath = $"{folderPath}/{Selection.activeObject.name}Mediator.cs";
        if (File.Exists(scriptPath))
        {
            Debug.LogError($"The script \"{scriptPath}\" already exists.");
            return;
        }

        var myPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
        // 获取 Prefab 的所有子对象
        GetNodeData(myPrefab);

        var txt = $@"using UnityEngine;
namespace PD
{{
    public {(isPartial ? "partial" : "")} class {Selection.activeObject.name}Mediator : NGuiFormMasterLogic
    {{
        public override string FacadeName
        {{
            get {{ return HomeFacade.Name; }}//TODO:修改FacadeName
        }}

        public new const string NAME = {$"\"{Selection.activeObject.name}Mediator\""};

        public override void OnConstruct(object userData)
        {{
            base.OnConstruct(userData);
            MediatorName = NAME;
        }}
";
        var txtEnd = $@"
    }}
}}";
        for (var i = 0; i < _nodeList.Count; i++)
        {
            var name = _nodeList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private GameObject {name};
";
            txt += str;
        }

        for (var i = 0; i < _imgList.Count; i++)
        {
            var name = _imgList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UISprite {name};
";
            txt += str;
        }

        for (var i = 0; i < _labelList.Count; i++)
        {
            var name = _labelList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UILabel {name};
";
            txt += str;
        }

        for (var i = 0; i < _btnList.Count; i++)
        {
            var name = _btnList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UIButton {name};
";
            txt += str;
        }

        for (var i = 0; i < _sliderList.Count; i++)
        {
            var name = _sliderList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"
        [SerializeField] private UISlider {name};
";
            txt += str;
        }

        for (var i = 0; i < _btnList.Count; i++)
        {
            var name = _btnList[i].name[2..];
            name = name[..1].ToUpper() + name[1..];
            var str = $@"


        public void On{name}Click()
        {{
           
        }}

";
            txt += str;
        }


        txt += txtEnd;
        EditorPrefs.SetString("creatorPath", assetPath);
        EditorPrefs.SetString("creatorName", myPrefab.name);
        EditorPrefs.SetString("creatorScriptPath", scriptPath);
        File.WriteAllText(scriptPath, txt);
        AssetDatabase.Refresh();
    }

    public static void AddComp()
    {
        var scriptName = EditorPrefs.GetString("creatorName");
        string[] guids = AssetDatabase.FindAssets(scriptName);

        if (guids.Length > 0)
        {
            var assetPath = EditorPrefs.GetString("creatorPath");
            string path = AssetDatabase.GUIDToAssetPath(guids[0]);
            MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
            System.Type type = monoScript.GetClass();
            var prefab =
                GameObject.Instantiate(
                    AssetDatabase
                        .LoadMainAssetAtPath(
                            assetPath)) as GameObject; // PrefabUtility.LoadPrefabContents(assetPath);//
            var component = prefab.AddComponent(type);
            // 获取所有字段
            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            var list = prefab.GetComponentsInChildren<Transform>(true).Where(a => a.name.StartsWith("m_")).ToList();

            foreach (FieldInfo field in fields)
            {
                // 判断字段是否被序列化
                var attributes = field.GetCustomAttributes(typeof(SerializeField), false);
                if (attributes.Length > 0)
                {
                    var temp = field.Name + "";
                    var name = temp[..1].ToLower() + temp[1..];
                    var go = list.FirstOrDefault(a => a.name == $"m_{name}");
                    if (go != null)
                    {
                        list.Remove(go);
                        var btn = go.GetComponent<UIButton>();
                        var slider = go.GetComponent<UISlider>();
                        var img = go.GetComponent<UISprite>();
                        var txt = go.GetComponent<UILabel>();
                        if (btn != null)
                        {
                            var str = $"On{field.Name}Click";
                            Debug.Log(str);
                            var delegateFunc1 = new EventDelegate(component as MonoBehaviour, str);
                            btn.onClick.Add(delegateFunc1);
                            field.SetValue(component, btn);
                        }
                        else if (slider != null)
                        {
                            field.SetValue(component, slider);
                        }
                        else if (img != null)
                        {
                            field.SetValue(component, img);
                        }
                        else if (txt != null)
                        {
                            field.SetValue(component, txt);
                        }
                        else
                        {
                            field.SetValue(component, go.gameObject);
                        }
                    }
                }
            }

            // GetNodeData(prefab);
            // PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, assetPath);
            PrefabUtility.SaveAsPrefabAssetAndConnect(prefab, assetPath, InteractionMode.AutomatedAction);
            Object.DestroyImmediate(prefab);
            AssetDatabase.Refresh();
        }
        else
        {
            Debug.LogError($"请先生成脚本");
        }
    }
    [MenuItem(MenuItemText1, true)]
    public static bool CreatorNormal()
    {
        if (!Selection.activeObject) return false;
        var prefabAssetType = PrefabUtility.GetPrefabAssetType(Selection.activeObject);
        return prefabAssetType != PrefabAssetType.NotAPrefab;

    }
    
    [MenuItem(MenuItemText3, true)]
    public static bool AddScript1()
    {
        if (!Selection.activeObject) return false;
        var prefabAssetType = PrefabUtility.GetPrefabAssetType(Selection.activeObject);
        return prefabAssetType != PrefabAssetType.NotAPrefab;

    }
    private static void GetNodeData(GameObject prefab)
    {
        Transform[] nodes = prefab.GetComponentsInChildren<Transform>(true);
        _nodeList = new List<GameObject>();
        _imgList = new List<UISprite>();
        _labelList = new List<UILabel>();
        _btnList = new List<UIButton>();
        _sliderList = new List<UISlider>();                                                                                              
        // 遍历所有子对象
        foreach (var node in nodes)
        {
            if (!node.gameObject.name.StartsWith("m_")) continue;
            var btn = node.GetComponent<UIButton>();
            var img = node.GetComponent<UISprite>();
            var label = node.GetComponent<UILabel>();
            var slider = node.GetComponent<UISlider>();
            if (btn != null)
            {
                _btnList.Add(btn);
            }
            else if (slider != null)
            {
                _sliderList.Add(slider);
            }
            else if (img != null)
            {
                _imgList.Add(img);
            }
            else if (label != null)
            {
                _labelList.Add(label);
            }
            else
            {
                _nodeList.Add(node.gameObject);
            }
        }
    }
}



使用方法

知识点

1.字符串使用@前缀,可以不使用\n换行。和$搭配使用时,字符串中如果有 { 需要用 {{ 两个大括号表示,第一个为转义。 字符串中有 ” 时,需要使用反斜杠 " 转义
2.判断物体是不是Prefab,PrefabUtility.GetPrefabAssetType(Selection.activeObject) != PrefabAssetType.NotAPrefab;
3.[MenuItem(MenuItemText2, true)] 第二个参数 bool值表示是否显示本扩展方法
4.获取指定脚本所有的Pulbic[SerializeField]的属性: type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)

;