需求
由于之前给服务器导出过.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个的物体),每次层级变动或者新增删除物体就需要重新导出一份数据。