Unity编辑器拓展-查找GameObject被哪些组件所引用


前言

有时需要查找一个GameObject有没被场景里的哪些组件引用,直接查效率比较低,而且还可能漏了,这里做了一个小工具来查找


一、效果图

在这里插入图片描述

二、思路

想知道一个组件有没有引用到我们的GameObject,可以获取这个组件的所有字段,即FieldInfo。 然后遍历所有的FiledInfo, 如果FiledInfo是GameObject或者是Component类型,那么我们即可获取到这个FieldInfo所对应的GameObject,接下来判断这个GameObject和我们想查询的GameObject是不是同一个。是的话,即代表这个组件引用了我们查询的GameObject.

三、实现

ok,既然有了思路,接下来,我们来实现这个功能。

1. 获取所有的物体

var objs = Resources.FindObjectsOfTypeAll(typeof(Transform));

Resources.FindObjectsOfTypeAll()方法找到的物体不仅包括 Hierarchy 窗口,也包括Project窗口里的预制体。一般情况下我们只想查找场景里的引用,预制体的不要。不然一般会查出两份一样的。接下来我们来过滤下

2. 过滤掉一些不要的物体

2.1 只获取是根节点的Transform

为什么只获取是根节点的Transform呢。因为我们可以通过rootTran.GetComponentsInChildren<Component>() 来拿到根节点和它所有子节点的组件。所以拿到了所有根节点,也就相当于拿到了所有组件。

    private static List<Transform> GetRootTranList(Object[] objs)
    {
    
    
        List<Transform> parentTranList = new List<Transform>();

        foreach (var o in objs)
        {
    
    
            var tran = o as Transform;
            if (tran.parent == null || tran.parent.name.Equals(CANVAS_ENVIROMENT)) 
            {
    
    
                //过滤掉预制体
                if (IsInHierarchy(tran.gameObject))
                {
    
    
                    parentTranList.Add(tran);
                }
            }
        }

        return parentTranList;
    }

看上面的代码:

  if (tran.parent == null || tran.parent.name.Equals("Canvas (Environment)")) 

根节点可以通过tran.parent == null 来判断,这个好理解。

那为什么父节点名字为"Canvas (Environment)"也算呢: 当我们双击一个UGUI预制体的时候,可以在Hierarchy里窗口看到它的层级,不过Unity会自动添加一个"Canvas (Environment)"的根节点,所以它的根节点也就变成了这个。这种情况我们也想查询,所以把它也加进来。

2.2 过滤预制体

怎么过滤预制体呢,可以通过 gameObject.scene.path 来判断. 在场景里的GameObject这个路径不为空,预制体则为空。

所以我们可以根据它是否为空来判断

要注意的是双击进来的UGUI预制体,即前面说的根节点为"Canvas (Environment)",这个路径也为空,但是我们想把它也加进来,所以当根节点名字为这个名字时,我们也返回true. 如以下代码

    private static bool IsInHierarchy(GameObject go)
    {
    
    
        if (!string.IsNullOrEmpty(go.scene.path))
        {
    
    
            return true;
        }else if (go.name.Equals(CANVAS_ENVIROMENT))
        {
    
    
            return true;
        }

        return false;
    }

3. 判断一个根节点里的所有组件是否引用了查询物体

接下来就到了关键的时候,如最前面的思路,我们拿到根节点的所有组件。对于任意一个组件,我们获取它所有的字段FiledInfo,然后判断字段FiledInfo的 (GameObject == 我们查询的物体),若等于,即该组件引用了我们查询的物体。代码如下

    private static bool FindOneReferenced(Transform rootTran, GameObject targetGo)
    {
    
    
        //获取这个节点和其子节点的所有组件
        Component[] coms = rootTran.GetComponentsInChildren<Component>();
        
        bool isFind = false;
        for (int i = 0; i < coms.Length; i++)
        {
    
                
            if (coms[i] == null)
            {
    
    
                continue;
            }
            //遍历一个组件的所有字段
            var fileList = coms[i].GetType().GetFields().ToList<FieldInfo>();
            for (int j = 0; j < fileList.Count; j++)
            {
    
    
                var fileInfo = fileList[j];
                var fileValue = fileInfo.GetValue(coms[i]); //关键代码,获得一个字段的引用对象
                GameObject fileGo = null;

                if (fileValue == null)
                {
    
    
                    continue;
                }
				
				//如果是GameObject
                if (typeof(GameObject) == fileValue.GetType())
                {
    
    
                    fileGo = (fileValue as GameObject)?.gameObject;
                }
                //如果继承了Component组件
                else if (typeof(Component).IsAssignableFrom(fileValue.GetType()))
                {
    
    
                    var tempComp = (fileValue as Component);
                    if (tempComp != null)
                        fileGo = tempComp.gameObject;
                    // fileGo = (fileValue as Component)?.gameObject; 如果改用此简写,可能会报 UnassignedReferenceException 异常...
                }

                if (fileGo != null && fileGo == curSelectedGo)
                {
    
    
                    isFind = true;
                    //在Hierarchy窗口显示查到的物体
                    EditorGUIUtility.PingObject(coms[i]);
                    Debug.Log($"找到引用,引用物体名:{coms[i]} 字段名:{fileInfo.Name}");
                }
            }
        }

        return isFind;
    }

四、完整代码

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.Build.Content;
using UnityEngine;

public class FindTools
{
    
    
    private static GameObject curSelectedGo = null;
    private const string CANVAS_ENVIROMENT = "Canvas (Environment)"; //双击UGUI的预制体,在Hierarchy窗口,此预制体的根节点会变为这个名字

    [MenuItem("GameObject/FindReference", false, 30)]
    public static void FindReferenced()
    {
    
       
        //Resources.FindObjectsOfTypeAll()方法找到的物体不仅包括 Hierarchy窗口,也包括Project窗口里的预制体。接下来会过滤掉预制体
        var objs = Resources.FindObjectsOfTypeAll(typeof(Transform));

        GameObject selectedGo = Selection.activeGameObject;
        if (selectedGo == null)
        {
    
    
            Debug.Log("请先选中要查找的物体");
            return;
        }

        curSelectedGo = selectedGo;

        var parentTranList = GetRootTranList(objs);
        bool isFind = false;
        for (int i = 0; i < parentTranList.Count; i++)
        {
    
    
            isFind = FindOneReferenced(parentTranList[i], curSelectedGo) || isFind;
        }

        if (!isFind)
        {
    
    
            Debug.LogWarning("没能找到引用");
        }
    }

    private static List<Transform> GetRootTranList(Object[] objs)
    {
    
    
        List<Transform> parentTranList = new List<Transform>();

        foreach (var o in objs)
        {
    
    
            var tran = o as Transform;
            if (tran.parent == null || tran.parent.name.Equals(CANVAS_ENVIROMENT)) 
            {
    
    
                //过滤掉预制体
                if (IsInHierarchy(tran.gameObject))
                {
    
    
                    parentTranList.Add(tran);
                }
            }
        }

        return parentTranList;
    }

    /// <summary>
    /// 判断一个GameObject是否在场景中。 用于过滤掉预制体
    /// </summary>
    /// <param name="go"></param>
    /// <returns></returns>
    private static bool IsInHierarchy(GameObject go)
    {
    
    
        if (!string.IsNullOrEmpty(go.scene.path))
        {
    
    
            return true;
        }else if (go.name.Equals(CANVAS_ENVIROMENT))
        {
    
    
            return true;
        }

        return false;
    }

    private static bool FindOneReferenced(Transform rootTran, GameObject targetGo)
    {
    
    
        //获取这个节点和其子节点的所有组件
        Component[] coms = rootTran.GetComponentsInChildren<Component>();
        
        bool isFind = false;
        for (int i = 0; i < coms.Length; i++)
        {
    
                
            if (coms[i] == null)
            {
    
    
                continue;
            }
            
            //遍历一个组件的所有字段
            var fileList = coms[i].GetType().GetFields().ToList<FieldInfo>();
            for (int j = 0; j < fileList.Count; j++)
            {
    
    
                var fileInfo = fileList[j];
                var fileValue = fileInfo.GetValue(coms[i]); //关键代码,获得一个字段的引用对象
                GameObject fileGo = null;

                if (fileValue == null)
                {
    
    
                    continue;
                }

				//是否是GameObject
                if (typeof(GameObject) == fileValue.GetType())
                {
    
    
                    fileGo = (fileValue as GameObject)?.gameObject;
                }
                //或者是否继承Component组件
                else if (typeof(Component).IsAssignableFrom(fileValue.GetType()))
                {
    
    
                    var tempComp = (fileValue as Component);
                    if (tempComp != null)
                        fileGo = tempComp.gameObject;
                    // fileGo = (fileValue as Component)?.gameObject; 如果改用此简写,可能会报 UnassignedReferenceException 异常...
                }

                if (fileGo != null && fileGo == curSelectedGo)
                {
    
    
                    isFind = true;
                    //在Hierarchy窗口显示查到的物体
                    EditorGUIUtility.PingObject(coms[i]);
                    Debug.Log($"找到引用,引用物体名:{coms[i]} 字段名:{fileInfo.Name}");
                }
            }
        }

        return isFind;
    }
}

总结

以上就是这次分享的内容,主要还是思路里的拿到所有字段FieldInfo,然后进行判断。其他都是根据这点进行展开。

猜你喜欢

转载自blog.csdn.net/aaa27987/article/details/118032288