天天看點

Unity 編輯器擴充 ( 案例1.在代碼中進行GameObject生成和指派儲存預知體 案例2  代碼生成器 案例3重新開機項目)

初步學習Unity EditorWindow 的擴充

案例1.在代碼中進行GameObject生成和指派儲存預知體  下面有GIF示範圖檔

  • 生成Gameobject 并對其進行指派
  • 序列化
  • 生成Prefabs
  • 編輯器視窗 EditorWindow

建立UIRoot類

using UnityEngine;
public class UIRoot : MonoBehaviour
{
    public Transform bg;
    public Transform common;
    public Transform uiPop;
    public Transform forward;
    [SerializeField]
    private Canvas mRootCanvas;
}
           

生成Canvas 等。。

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

public class CreateUIRootPanel : EditorWindow
{
    static CreateUIRootPanel window;
    [MenuItem("編輯器擴充/1.SetUp(UIRoot)", true)]
    static bool ValidateUIRoot()
    {
        return !GameObject.Find("UIRoot");
    }

    [MenuItem("編輯器擴充/1.SetUp(UIRoot)", false)]
    static void MenuItem()
    {
        window = GetWindow<CreateUIRootPanel>();
        window.Show();
    }
    private string mWidth = "720";
    private string mHeight = "1280";
    private void OnGUI()
    {
        GUILayout.BeginHorizontal();
        //var width = GUILayout.TextField("width", GUILayout.Width(50));
        mWidth = GUILayout.TextField(mWidth, GUILayout.Width(50));
        GUILayout.Label("X", GUILayout.Width(15));
        mHeight = GUILayout.TextField(mHeight, GUILayout.Width(55));

        GUILayout.EndHorizontal();
        if (GUILayout.Button("Build"))
        {
            Debug.Log("build");
            SetUP(float.Parse(mWidth), float.Parse(mHeight));
            //  window.Close();
        }
    }
    public static void SetUP(float width, float height)
    {
        Debug.Log("編輯器擴充");
        // GameObject go = Resources.Load<GameObject>("UIroot");
        // GameObject.Instantiate(go);
        var UIRootObj = new GameObject("UIRoot");
        UIRootObj.layer = LayerMask.NameToLayer("UI");

        UIRoot UIScript = UIRootObj.AddComponent<UIRoot>();

        var canvas = new GameObject("Canvas");

        canvas.transform.SetParent(UIRootObj.transform);
        canvas.layer = LayerMask.NameToLayer("UI");
        canvas.AddComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
        //canvas.AddComponent<CanvasGroup>();
        //CanvasScaler 
        CanvasScaler canvasScaler = canvas.AddComponent<CanvasScaler>();

        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = new Vector2(width, height);
        canvas.AddComponent<GraphicRaycaster>();

        //EventSystem
        var eventSystem = new GameObject("EventSystem");

        eventSystem.layer = LayerMask.NameToLayer("UI");
        eventSystem.transform.SetParent(UIRootObj.transform);
        eventSystem.AddComponent<EventSystem>();
        eventSystem.AddComponent<StandaloneInputModule>();

        //BG
        var bgObj = new GameObject("Bg");
        bgObj.AddComponent<RectTransform>();
        bgObj.transform.SetParent(canvas.transform);
        bgObj.transform.localPosition = Vector3.zero;
        UIScript.bg = bgObj.transform;
        //Common
        var commonObj = new GameObject("Common");
        commonObj.AddComponent<RectTransform>();
        commonObj.transform.SetParent(canvas.transform);
        commonObj.transform.localPosition = Vector3.zero;
        UIScript.common = commonObj.transform;
        //PopUI
        var popUI = new GameObject("PopUI");
        popUI.AddComponent<RectTransform>();
        popUI.transform.SetParent(canvas.transform);
        popUI.transform.localPosition = Vector3.zero;
        UIScript.uiPop = popUI.transform;
        //ForwardObj
        var forwardObj = new GameObject("Forward");
        forwardObj.AddComponent<RectTransform>();
        forwardObj.transform.SetParent(canvas.transform);
        forwardObj.transform.localPosition = Vector3.zero;
        UIScript.forward = forwardObj.transform;

        ///序列化腳本 添加 UIRoot中的Private的值
        var uirootScriptSerializedObj = new SerializedObject(UIScript);
        uirootScriptSerializedObj.FindProperty("mRootCanvas").objectReferenceValue = canvas.GetComponent<Canvas>();
        uirootScriptSerializedObj.ApplyModifiedProperties();



        var saveFolder = Application.dataPath + "/Resources";
        if (!Directory.Exists(saveFolder))
        {
            Directory.CreateDirectory(saveFolder);
        }
        var saveFilePath = saveFolder + "/UIRoot.Prefab";
        // PrefabUtility.CreatePrefab(saveFilePath, UIRootObj);
        bool IsLoad = false;
        PrefabUtility.SaveAsPrefabAsset(UIRootObj, saveFilePath, out IsLoad);
        Debug.Log(string.Format(" 儲存預知體[{0}]", IsLoad ? "成功" : "失敗"));
    }
}
           

案例1最終效果 

Unity 編輯器擴充 ( 案例1.在代碼中進行GameObject生成和指派儲存預知體 案例2  代碼生成器 案例3重新開機項目)

 案例2  代碼生成器  下面有GIF示範圖檔

  • 在Hierarchy生成腳本指令菜單
  • 生成腳本
  • 添加标簽
  • 搜尋并且生成功能
  • 生成代碼分為兩個腳本
  • 命名空間的設定 (包括在windows視窗進行設定命名空間)
  • 生成路徑選擇路徑修改路徑和儲存路徑
  • 建立Prefab
  • 觸發自動編譯
  • 快捷鍵的支援
  • 生成腳本的元件類型指定
  • 自定義面闆

主要的生成代碼腳本 CreateComponentCode   在Editor檔案夾下

using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEditor.Callbacks;
using EditorExtension;
public class CreateComponentCode : EditorWindow
{
    [MenuItem("編輯器擴充/2.Build  配合 GameObject的@EdtiorExtemsion-CreateCode使用     %#T", false, 2  )]
    public static void OpenMenu()
    {
        GetWindow<CreateComponentCode>().Show();
    }
    [MenuItem("GameObject/@(Alt+A)EdtiorExtemsion-Add View   %A", false, 0)]
    public static void AddView()
    {
        GameObject currentGameObject = Selection.gameObjects.First();
        if (currentGameObject == null)
        {
            Debug.LogError("請選擇Gameobject");
            return;
        }
        var view = currentGameObject.GetComponent<CodeGenderateInfo>();
        if (!view)
        {
            currentGameObject.AddComponent<CodeGenderateInfo>();
        }

    }
    [MenuItem("GameObject/@(Alt+B)EdtiorExtemsion-Bind     %B", false, 0)]
    public static void Bind()
    {
        GameObject currentGameObject = Selection.gameObjects.First();
        if (currentGameObject == null)
        {
            Debug.LogError("請選擇Gameobject");
            return;
        }
        var view = currentGameObject.GetComponent<UIMark>();
        if (!view)
        {
            currentGameObject.AddComponent<UIMark>();
        }

    }
    private void OnGUI()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("NameSpace");
        NamespaceSettingsData.Namespace = GUILayout.TextField(NamespaceSettingsData.Namespace);
        GUILayout.EndHorizontal();
    }


    static List<UIMarkInfo> uIMarkInfos = new List<UIMarkInfo>();

    [MenuItem("GameObject/@(Alt+C)EdtiorExtemsion-CreateCode  %C", true, 0)]
    static bool ValidateCreateCode()
    {
        Debug.Log("Create Code");
        GameObject currentGameObject = Selection.gameObjects.First();
        if (currentGameObject == null)
        {
            Debug.LogError("請選擇Gameobject");
            return false;
        }
        return currentGameObject.GetComponent<ViewMark>();

    }

    [MenuItem("GameObject/@EdtiorExtemsion-CreateCode", false, 0)]
    static void CreateCode()
    {
        Debug.Log("Create Code");
        GameObject currentGameObject = Selection.gameObjects.First();
        if (currentGameObject == null)
        {
            Debug.LogError("請選擇Gameobject");
            return;
        }
        var mName = currentGameObject.name;

        var scriptsFolder = Application.dataPath + "/Scripts";
        var generateInfo = currentGameObject.GetComponent<CodeGenderateInfo>();
        if (generateInfo != null)
        {
            scriptsFolder = generateInfo.ScriptsFolder;
        }
        if (!Directory.Exists(scriptsFolder))
        {
            Directory.CreateDirectory(scriptsFolder);
        }

        uIMarkInfos.Clear();
        //搜尋所有綁定
        SearchBinds("", currentGameObject.transform, uIMarkInfos);


        ComponentTemplate.Write(mName, scriptsFolder);
        ComponentDesignerTemplate.Write(mName, scriptsFolder, uIMarkInfos);


        EditorPrefs.SetString("Load", mName);
        //重新整理目錄
        AssetDatabase.Refresh();
    }
    static void SearchBinds(string basePath, Transform transform, List<UIMarkInfo> uIMarks)
    {
        var uiMark = transform.GetComponent<UIMark>();
        var isRoot = string.IsNullOrEmpty(basePath);
        if (uiMark && !isRoot)
        {
            uIMarks.Add(new UIMarkInfo() { FindPath = basePath, Name = transform.name, ComponentName = uiMark.ComponentName });
        }
        foreach (Transform childTrans in transform)
        {
            SearchBinds(isRoot ? childTrans.name : basePath + "/" + childTrans.name, childTrans, uIMarks);
        }
    }
    //系統的事件   DidReloadScripts  編譯完成以後就會調用
    //将此屬性添加到方法中,以便在重新加載腳本後獲得通知。
    //DidReloadScript有一個提供訂單索引的選項。這允許您更改調用回調的順序。(Builtin回調的值始終為0)。
    [DidReloadScripts]
    static void AddComponent2GameObject()
    {
        Debug.Log("DidReloadScripts" + System.DateTime.Now);
        string generateClassGame = EditorPrefs.GetString("Load");

        Debug.Log(generateClassGame);
        if (string.IsNullOrWhiteSpace(generateClassGame))
        {
            Debug.Log("不進行操作");
            EditorPrefs.DeleteKey("Load");
        }
        else
        {
            Debug.Log("繼續操作");
            // 1.擷取目前項目中所有的 assembly (可以了解為 代碼編譯好的 dll)
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            // 2.擷取編輯器環境(dll)
            var defaultAssetBly = assemblies.First(assembly => assembly.GetName().Name == "Assembly-CSharp");
            //添加Namespace
            var typeName = NamespaceSettingsData.Namespace + "." + generateClassGame;
            Debug.Log(typeName);
            Type type = defaultAssetBly.GetType(typeName);
            Debug.Log(type);
            if (type == null)
            {
                Debug.Log("編譯失敗~");
                return;
            }
            var currentGameObject = GameObject.Find(generateClassGame);
            var scriptComponent = currentGameObject.GetComponent(type);
            if (!scriptComponent)
            {
                scriptComponent = currentGameObject.AddComponent(type);
            }
            var serialiedScript = new SerializedObject(scriptComponent);
            uIMarkInfos.Clear();
            //搜尋所有綁定
            SearchBinds("", currentGameObject.transform, uIMarkInfos);
            foreach (UIMarkInfo markInfo in uIMarkInfos)
            {
                //   var name=
                var name =markInfo.Name;
                serialiedScript.FindProperty(name).objectReferenceValue = currentGameObject.transform.Find(markInfo.FindPath).gameObject;
                Debug.Log(markInfo.FindPath);
            }
            var codeGenerateInfo = currentGameObject.GetComponent<CodeGenderateInfo>();
            if (codeGenerateInfo != null)
            {
                serialiedScript.FindProperty("ScriptsFolder").stringValue = codeGenerateInfo.ScriptsFolder;

                var generatePrefab = codeGenerateInfo.GeneratePrefab;
                var prefabForder = codeGenerateInfo.PrefabFolder;
                serialiedScript.FindProperty("PrefabFolder").stringValue = prefabForder;
                serialiedScript.FindProperty("GeneratePrefab").boolValue = generatePrefab;
                var fullPrefabFolder = prefabForder.Replace("Assets", Application.dataPath);
                serialiedScript.ApplyModifiedPropertiesWithoutUndo();
                if (codeGenerateInfo.GetType() == type)
                {
                }
                else
                {
                    DestroyImmediate(codeGenerateInfo);
                }
                if (generatePrefab)
                {
                    if (!Directory.Exists(fullPrefabFolder))
                    {
                        Directory.CreateDirectory(fullPrefabFolder);
                    }
                    // bool isFinish = false; //兩種方法都可以
                    // PrefabUtility.SaveAsPrefabAsset(currentGameObject, fullPrefabFolder + "/" + currentGameObject.name + ".Prefab", out isFinish);
                    // Debug.LogFormat("儲存預制體【{0}】", isFinish ? "成功" : "失敗");
                    PrefabUtility.SaveAsPrefabAssetAndConnect(currentGameObject, fullPrefabFolder + "/" + currentGameObject.name + ".prefab", InteractionMode.AutomatedAction);
                }
            }
            else
            {
                serialiedScript.FindProperty("ScriptsFolder").stringValue = "Assets/Scripts";
                serialiedScript.ApplyModifiedPropertiesWithoutUndo();
            }
            AssetDatabase.Refresh();

            EditorPrefs.DeleteKey("Load");
        }
    }
}
public class ComponentDesignerTemplate
{
    public static void Write(string name, string scriptsFolder, List<UIMarkInfo> uIMarkInfos)
    {
        var scriptFile = string.Format("{0}/{1}.Designer.cs", scriptsFolder, name);

        // if (File.Exists(scriptFile))
        // {
        //     File.Delete(scriptFile);
        // }

        StreamWriter writer = File.CreateText(scriptFile);
        writer.WriteLine("\tusing UnityEngine;");
        writer.WriteLine();
        writer.WriteLine("\t//1.請在菜單編輯器擴充/NamespaceSetting 設定命名空間");
        writer.WriteLine("\t//2.命名空間更改後 生成代碼後 需要吧邏輯代碼檔案(非Designer)的命名空間手動更改");
        writer.WriteLine($"\tnamespace {NamespaceSettingsData.Namespace}");
        writer.WriteLine("\t{");
        writer.WriteLine($"\t\tpublic partial class {name}  ");
        writer.WriteLine("\t\t{");
        foreach (UIMarkInfo uiMarkInfo in uIMarkInfos)
        {
            writer.WriteLine($"\t\tpublic {uiMarkInfo.ComponentName} { uiMarkInfo.Name};");
        }
        writer.WriteLine();
        writer.WriteLine("\t\t}");
        writer.WriteLine("\t}");
        writer.Close();
    }
}
public class ComponentTemplate
{
    public static void Write(string name, string scriptsFolder)
    {
        var scriptFile = string.Format("{0}/{1}.cs", scriptsFolder, name);
        if (File.Exists(scriptFile))
        {
            return;
        }
        StreamWriter writer = File.CreateText(scriptFile);
        //保證每次生成的東西都不一樣 就會執行編譯操作
        writer.WriteLine($"//Generate ID:{Guid.NewGuid().ToString()}");
        writer.WriteLine("\tusing UnityEngine;");
        writer.WriteLine("\tusing EditorExtension;");
        writer.WriteLine();
        writer.WriteLine("\t//1.請在菜單編輯器擴充/NamespaceSetting 設定命名空間");
        writer.WriteLine("\t//2.命名空間更改後 生成代碼後 需要吧邏輯代碼檔案(非Designer)的命名空間手動更改");
        writer.WriteLine($"\tnamespace {NamespaceSettingsData.Namespace} ");
        writer.WriteLine("\t{");
        writer.WriteLine($"\t\tpublic partial class {name} : CodeGenderateInfo");
        writer.WriteLine("\t\t{");
        writer.WriteLine("\t\tvoid Start()");
        writer.WriteLine("\t\t{");
        writer.WriteLine("\t\t//Code here");
        writer.WriteLine("\t\t}");
        writer.WriteLine("\t\t}");
        writer.WriteLine();
        writer.WriteLine("\t}");
        writer.Close();
    }
}
public class UIMarkInfo
{
    public string FindPath;
    public string ComponentName;
    public string Name;

}

public class NamespaceSettingsData
{
    private readonly static string NAMESPACE_KEY = Application.productName + "@NAMESPACE";
    public static string Namespace
    {
        get
        {
            var retNamespace = EditorPrefs.GetString(NAMESPACE_KEY, "DefaultNamespace");
            return string.IsNullOrEmpty(retNamespace) ? "DefaultNamespace" : retNamespace;
        }
        set { EditorPrefs.SetString(NAMESPACE_KEY, value); }
    }
    public static bool IsDefaultNamespace => Namespace == "DefaultNamespace";
}           

 UIMark腳本 标記

using UnityEngine;
[AddComponentMenu("EditorExension/Mark")]
public class UIMark : MonoBehaviour
{
    public enum GetComponentType
    {

        Transform,
        MeshRenderer,
        RectTransform,
        Rigibody,
        BoxCollider,
        MeshCollider,


    }
    public GetComponentType componentType = GetComponentType.Transform;

    public string ComponentName
    {

        get
        {
            // if (GetComponent<MeshRenderer>())
            // {
            //     return "MeshRenderer";
            // }

            return componentType.ToString();
        }
    }
}
           

ViewMark  Gameobject必須挂載  ViewMark才能進行生成腳本

using UnityEngine;
public class ViewMark : MonoBehaviour
{
}
           

生成腳本的配置腳本 腳本路徑 生成prefab 生成brefab的路徑

using UnityEngine;
namespace EditorExtension
{
    [ExecuteInEditMode]
    public class CodeGenderateInfo : MonoBehaviour
    {
        [HideInInspector]
        public string ScriptsFolder;
        [HideInInspector]

        public string PrefabFolder;
        [HideInInspector]

        public bool GeneratePrefab = false;
        private void Awake()
        {
            ScriptsFolder = "Assets/Scripts";
            PrefabFolder = "Assets/Prefabs";
        }
    }
}           

 修改挂載代碼的腳本樣式 依然放在Editor下

using UnityEditor;
using UnityEngine;

namespace EditorExtension
{
    [CustomEditor(typeof(CodeGenderateInfo), editorForChildClasses: true)]
    public class CodeGenerateInfoInspector : Editor
    {

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            var codeGenerateInfo = target as CodeGenderateInfo;
            GUILayout.BeginVertical("box");
            GUILayout.Label("代碼生成部分", new GUIStyle()
            {
                fontSize = 20,
                fontStyle = FontStyle.Bold
            });
            GUILayout.BeginHorizontal();
            GUILayout.Label("Scripts Generate Folder : ", GUILayout.Width(150));
            codeGenerateInfo.ScriptsFolder = GUILayout.TextField(codeGenerateInfo.ScriptsFolder);
            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal();
            GUILayout.Label("GeneratePrefab :", GUILayout.Width(150));
            codeGenerateInfo.GeneratePrefab = GUILayout.Toggle(codeGenerateInfo.GeneratePrefab, "Generete Perfab");
            GUILayout.EndHorizontal();
            if (codeGenerateInfo.GeneratePrefab)
            {
                GUILayout.BeginHorizontal();
                GUILayout.Label("Prefab Generate Folder : ", GUILayout.Width(150));
                codeGenerateInfo.PrefabFolder = GUILayout.TextField(codeGenerateInfo.PrefabFolder);
                GUILayout.EndHorizontal();
            }

            GUILayout.EndVertical();

        }
    }
}           

案例2最終效果如下:

案例3重新開機項目   下面有GIF示範圖檔

using UnityEditor;
using UnityEngine;
public class ReopenProject
{
    [MenuItem("編輯器擴充/3.(Alt+R)ReopenProject  &R", false, 3)]
    static void DOReopenProject()
    {
        EditorApplication.OpenProject(Application.dataPath.Replace("Assets", string.Empty));
    }
}