Unity自定義Hierarchy面板案例(顯示GameObject的InstanceID),以及通過InstanceID找到場景中的對應GameObject

需求

由於之前給服務器導出過.obj文件(類似於導出遊戲地圖中每個GameObject的某些信息),其中會遇到一個問題,就是如果這些文件中有一些文件有錯誤,我們如何定位到遊戲地圖中對應的GameObject。

最簡單的就是導出的時候帶name信息,然後在Hierarchy面板中搜索對應的name。但是由於一個大地圖,可能有成千上萬個GameObject,然後美術在製作的時候可能很多都是重複的,利用複製黏貼生成。導致實際上會有很多相同name的GameObject,也不太可能說讓美術們一個個改名來保證唯一性。

我們知道GameObject有個InstanceID,可以作爲唯一標識,但是這個ID我們在Hierarchy面板中無法直觀的看見,這時候就需要我們加點小功能,來實現下面兩個功能:

1.在Hierarchy面板中,顯示選中的GameObject的InstanceID

2.創建一個自定義窗口,在裏面可以通過輸入一個InstanceID,來定位到Hierarchy對應的GameObject

效果圖如下:

這樣我們就可以通過導出文件的時候帶上InstanceID屬性,當發現某些文件有問題時,通過這些InstanceID來定位出問題的GameObject。

備註:同一個GameObject,gameobject.GetInstanceID() 和 transform.GetInstanceID()不一樣,我們需要使用gameobject的instanceid。

 

實現

我們知道Inspector,Scene面板都可以做一些自定義功能,那麼我們如何拓展Hierarchy面板呢,通過查找,發現有一個EditorApplication.hierarchyWindowItemOnGUI的API可以幫助我們實現。

這是一個委託方法,HierarchyWindowItemCallback(int instanceID, Rect selectionRect)中兩個參數分別是Hierarchy面板中每個Item對應的GameObject的InstanceID,以及每個Item的座標信息。我們可以利用這兩個信息在每個Item進行一些自定義UI的添加,添加方法類似EditorWindow,OnGUI中的使用。

GameObject go = EditorUtility.InstanceIDToObject(instanceId) as GameObject;

我們可以利用這個方法來通過InstanceID獲取對應的GameObject。

接下來,來看看具體的代碼實現,下面代碼都需要放在Editor目錄下。

(注:本來想利用Hierarchy中的Search功能,通過一些自定義搜索規則來直接在Hierarchy顯示對應的結果,但是除了找到一個EditorApplication.searchChanged搜索框內容改變的API外,沒有更多的信息了。)

首先創建一個CustomHierarchy類,用於拓展Hierarchy

using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
public class CustomHierarchy
{
    static GUIStyle m_style;
    static int m_isShowInstanceIDTagValue;
    const string IsShowInstanceIDTag = "IsShowInstanceIDTag";

    public static bool isShowInstanceID { get; private set; }

    static CustomHierarchy()
    {
        m_style = new GUIStyle();
        m_style.alignment = TextAnchor.MiddleRight;
        m_style.normal.textColor = Color.gray;

        m_isShowInstanceIDTagValue = PlayerPrefs.GetInt(IsShowInstanceIDTag, 0);
        isShowInstanceID = false;
        if (m_isShowInstanceIDTagValue == 1)
            OpenShowInstanceID();
    }    

    public static void OpenShowInstanceID()
    {
        if (!isShowInstanceID)
        {
            isShowInstanceID = true;
            PlayerPrefs.SetInt(IsShowInstanceIDTag, 1);
            EditorApplication.hierarchyWindowItemOnGUI += ShowInstanceID;
            EditorApplication.RepaintHierarchyWindow();
        }
    }

    public static void CloseShowInstanceID()
    {
        if (isShowInstanceID)
        {
            isShowInstanceID = false;
            PlayerPrefs.SetInt(IsShowInstanceIDTag, 0);
            EditorApplication.hierarchyWindowItemOnGUI -= ShowInstanceID;
            EditorApplication.RepaintHierarchyWindow();
        }
    }

    static void ShowInstanceID(int instanceId, Rect selectionRect)
    {
        //顯示Hierarchy選中的GameObject的InstanceID
        if (instanceId == Selection.activeInstanceID)
        {
            Rect rect = new Rect(50, selectionRect.y, selectionRect.width, selectionRect.height);
            GUI.Label(rect, instanceId.ToString(), m_style);
        }
    }
}

然後我們自定義一個Windows來實現我們的小功能

using UnityEditor;
using UnityEngine;

public class ToolsWindow : EditorWindow
{
    string m_ids = "";//輸入的InstanceID

    string m_log = "";//log信息
    Vector2 m_logScroll;

    public void OnGUI()
    {
        GUILayout.BeginVertical();
        GUILayout.Space(10);
        //查找GameObject
        {
            GUILayout.Label("1.通過GameObject的唯一ID(InstanceID)定位");
            GUILayout.BeginHorizontal();
            m_ids = GUILayout.TextField(m_ids, GUILayout.Width(120), GUILayout.Height(20));
            if (GUILayout.Button("定位", GUILayout.Width(60), GUILayout.Height(20)))
            {
                m_log = string.Empty;
                if (int.TryParse(m_ids, out int id))
                {
                    GameObject go = EditorUtility.InstanceIDToObject(id) as GameObject;
                    if (go != null)
                    {
                        Selection.activeGameObject = go; //在Hierarchy窗口選中該GameObject
                        m_log = $"查找成功 Name爲{go.name}";
                    }
                    else
                        m_log = $"沒有找到ID爲{id}的GameObject";
                }
                else
                    m_log = "請輸入正確的ID";
            }

            GUILayout.EndHorizontal();
        }
        GUILayout.Space(10);
        //是否開啓Hierarchy顯示InstanceID的功能
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label("2.是否開啓Hierarchy面板顯示InstanceID的功能", GUILayout.Width(260), GUILayout.Height(20));

            if (GUILayout.Toggle(CustomHierarchy.isShowInstanceID, ""))
                CustomHierarchy.OpenShowInstanceID();
            else
                CustomHierarchy.CloseShowInstanceID();
            GUILayout.EndHorizontal();
        }
        GUILayout.Space(10);
        //顯示Log
        {
            if (!string.IsNullOrEmpty(m_log))
            {
                m_logScroll = GUILayout.BeginScrollView(m_logScroll);
                m_log = EditorGUILayout.TextArea(m_log, GUILayout.Height(80));
                EditorGUILayout.EndScrollView();
            }
        }
        GUILayout.EndVertical();
    }
}

然後隨便找個地方添加菜單即可

public class EditorMenu
{
    [MenuItem("Tools/ToolsWindow")]
    static void ShowToolsWindow()
    {
        var window = (ToolsWindow)EditorWindow.GetWindow(typeof(ToolsWindow), false, "Tools Window");
        window.Show();
    }
}

 

補充:上面的方法會存在一個問題,由於InstanceID是會變化的(不同機器,或者每次啓動Unity,切場景等情況,都不一樣),所以我們需要使用其他方法來保證唯一且不變性。一開始想用LocalId(Local Identfier In File)但是發現很多情況下Hierarchy中的GameObject的LocalId爲0。因此後面想到的方法就是用索引(transform.GetSiblingIndex())來處理(例如2-5-4,即當前場景第2個物體,其子物體第5個,再其子物體第4個的物體),每次層級變動或者新增刪除物體就需要重新導出一份數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章