需求
由於之前給服務器導出過.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個的物體),每次層級變動或者新增刪除物體就需要重新導出一份數據。