文章目录
前言
有时需要查找一个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,然后进行判断。其他都是根据这点进行展开。