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可以帮助我们实现。

扫描二维码关注公众号,回复: 8727121 查看本文章

这是一个委托方法,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个的物体),每次层级变动或者新增删除物体就需要重新导出一份数据。

发布了71 篇原创文章 · 获赞 160 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/wangjiangrong/article/details/103787340