天天看點

Unity編輯器擴充——自動生成UI界面腳本

一:前言

對于面闆指派或Find綁定UI元件,我們可以使用一種工具化的方式去自動生成代碼并綁定對象,增加效率

分為logic和view,view層是UI界面上的元件,每次都會自動生成并覆寫,logic層是邏輯

二:使用

Unity編輯器擴充——自動生成UI界面腳本

例如一個UI界面,我們隻需要做成預制體并在Project下右鍵預制體,選擇AutoGen/Create View則會自動生成view和logic兩個腳本,logic是我們要編寫邏輯的腳本,view是每次都會自動生成并覆寫的腳本

三:說明

——以下幾個路徑都是可以自定義的(view和logic生成的路徑、view和logic模版檔案的路徑)

Unity編輯器擴充——自動生成UI界面腳本

——可以自定義忽略的元件類型清單

四:代碼實作 

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System;

public class AutoGenCode
{
    //logic層代碼路徑
    const string LogicDir = "Assets/AutoGen/Logic";
    //view層代碼路徑
    const string ViewDir = "Assets/AutoGen/View";
    //logic層模版檔案路徑
    const string LogicTempletePath = "Assets/AutoGen/LogicTemplete.txt";
    //view層模版檔案路徑
    const string ViewTempletePath = "Assets/AutoGen/ViewTemplete.txt";

    //命名空間模闆
    const string NameSpaceTemplete = "using {0};";
    //字段模闆
    const string FieldTemplete = "public {0} {1};\t";
    //方法模闆
    const string MethodTemplete = "{0} = gameObject.transform.Find(\"{1}\").GetComponent<{2}>();\t\t";

    /// <summary>
    /// 忽略的元件類型清單
    /// </summary>
    static List<Type> IgnoreComponentTypeList = new List<Type>()
    {
        typeof(CanvasRenderer),
        typeof(RectTransform),
    };

    [MenuItem("Assets/AutoGen/Create View", priority = 0)]
    static void CreateLogicAndView()
    {
        GameObject go = Selection.activeGameObject;
        //判斷是否是prefab
        if (PrefabUtility.GetPrefabAssetType(go) != PrefabAssetType.Regular)
        {
            Debug.LogWarning("選擇的不是預制體,選擇的對象:" + go.name);
            return;
        }
        if (!Directory.Exists(ViewDir))
        {
            Directory.CreateDirectory(ViewDir);
        }
        if (!Directory.Exists(LogicDir))
        {
            Directory.CreateDirectory(LogicDir);
        }

        string className = go.name + "View";
        StringBuilder fieldContent = new StringBuilder();
        StringBuilder methodContent = new StringBuilder();
        StringBuilder nameSpaceContent = new StringBuilder();
        nameSpaceContent.AppendLine(NameSpaceTemplete.Replace("{0}", "UnityEngine"));//必須有UnityEngine命名空間

        string logicTempleteContent = File.ReadAllText(LogicTempletePath, Encoding.UTF8);
        string viewTempleteContent = File.ReadAllText(ViewTempletePath, Encoding.UTF8);
        string logicPath = LogicDir + "/" + go.name + "Logic.cs";
        string viewPath = ViewDir + "/" + go.name + "View.cs";

        List<string> tempNameSpaceList = new List<string>();

        //計算所有子物體元件資料
        List<ComponentInfo> infoList = new List<ComponentInfo>();
        CalcComponentInfo("", go.transform, infoList);
        foreach (var tempInfo in infoList)
        {
            //字段
            string tempFieldStr = FieldTemplete.Replace("{0}", tempInfo.TypeStr);
            tempFieldStr = tempFieldStr.Replace("{1}", tempInfo.FieldName);
            fieldContent.AppendLine(tempFieldStr);

            //綁定方法
            string tempMethodStr = MethodTemplete.Replace("{0}", tempInfo.FieldName);
            tempMethodStr = tempMethodStr.Replace("{1}", tempInfo.Path);
            tempMethodStr = tempMethodStr.Replace("{2}", tempInfo.TypeStr);
            methodContent.AppendLine(tempMethodStr);

            //命名空間
            if (!tempNameSpaceList.Contains(tempInfo.NameSpace))
            {
                string tempNameSpaceStr = NameSpaceTemplete.Replace("{0}", tempInfo.NameSpace);
                tempNameSpaceList.Add(tempInfo.NameSpace);
                nameSpaceContent.AppendLine(tempNameSpaceStr);
            }
        }

        //logic層腳本
        if (!File.Exists(logicPath))
        {
            using (StreamWriter sw = new StreamWriter(logicPath))
            {
                string content = logicTempleteContent;
                content = content.Replace("#CLASSNAME#", className);
                sw.Write(content);
                sw.Close();
            }
        }
        //view層腳本
        using (StreamWriter sw = new StreamWriter(viewPath))
        {
            string content = viewTempleteContent;
            content = content.Replace("#NAMESPACE#", nameSpaceContent.ToString());
            content = content.Replace("#CLASSNAME#", className);
            content = content.Replace("#FIELD_BIND#", fieldContent.ToString());
            content = content.Replace("#METHOD_BIND#", methodContent.ToString());
            sw.Write(content);
            sw.Close();
        }

        AssetDatabase.Refresh();
    }

    /// <summary>
    /// 計算所有子物體元件資料
    /// </summary>
    static void CalcComponentInfo(string path, Transform child, List<ComponentInfo> infoList)
    {
        bool isRoot = string.IsNullOrEmpty(path);
        if (!isRoot
            && IsVaildField(child.name))
        {
            var componentList = child.GetComponents<Component>();
            foreach (var tempComponent in componentList)
            {
                ComponentInfo info = new ComponentInfo()
                {
                    Path = path,
                    go = child.gameObject,
                    NameSpace = tempComponent.GetType().Namespace,
                    TypeStr = tempComponent.GetType().Name,
                };
                if (!HaveSameComponentInfo(info, infoList)
                    && !IgnoreComponentTypeList.Contains(tempComponent.GetType()))
                {
                    infoList.Add(info);
                }
            }
        }

        foreach (Transform tempTrans in child.transform)
        {
            CalcComponentInfo(isRoot ? tempTrans.name : path + "/" + tempTrans.name, tempTrans.transform, infoList);
        }
    }

    /// <summary>
    /// 是否為合法的字段名
    /// </summary>
    static bool IsVaildField(string goName)
    {
        if (goName.Contains("_"))
        {
            if (int.TryParse(goName[0].ToString(), out _))
            {
                Debug.LogWarning("字段名不能以數字開頭:, goName :" + goName);
                return false;
            }
            return true;
        }
        return false;
    }

    /// <summary>
    /// 是否有相同的元件資料
    /// </summary>
    static bool HaveSameComponentInfo(ComponentInfo info, List<ComponentInfo> infoList)
    {
        foreach (var tempInfo in infoList)
        {
            if (tempInfo.FieldName == info.FieldName)
            {
                Debug.LogWarning("子物體名重複:, goName :" + info.go.name);
                return true;
            }
        }
        return false;
    }
}

/// <summary>
/// 元件資料
/// </summary>
public class ComponentInfo
{
    public string Path;
    public GameObject go;
    public string NameSpace;
    public string TypeStr;
    public string FieldName
    {
        get
        {
            return $"{go.name}_{TypeStr}";
        }
    }
}