|
Unity中的GameObject查找
系列:Unity: 1 Million Traps and Pitfalls
在Unity中经常有根据名字查找GameObject的需求,这些需求有不同的实现方式,比如常见的GameObject.Find和Transform.Find。这些API互相相似,但是又有不同的使用方式。所以这里使用调研的方式,明确它们的功能,并提出其他查找方案的实现思路。
本文使用的Unity版本为2021.2.0。
1. 在打开的场景里查找GameObject
1.1 GameObject.Find
public static GameObject Find(string name);
GameObject类下公有的静态函数,至于具体功能,我们可以引用一下Unity文档的内容:
Finds a GameObject by name and returns it.
This function only returns active GameObjects. If no GameObject with name can be found, null is returned. If name contains a '/' character, it traverses the hierarchy like a path name.
For performance reasons, it is recommended to not use this function every frame. Instead, cache the result in a member variable at startup. or use GameObject.FindWithTag.
Note: If you wish to find a child GameObject, it is often easier to use Transform.Find.
Note: If the game is running with multiple scenes then Find will search in all of them. 正如文档上所说,是通过name这个参数,GameObject.Find会在所有运行着的场景中寻找处于Active状态的游戏对象。可以传入字符串作为参数name,如果参数中带有'/’,则被视为是类似文件名的层级对象名称来参与查找。
当然看上面这个描述,很多地方还是比较模糊的,这里实践之后来明确一下。
首先是每一个GameObject有两个和active状态有关的属性,分别是activeSelf,和activeInHierarchy,前者代表这个GameObject节点自己是Active的;后者代表它在所处的层级中都是激活的,也就是说它的父节点,它父节点的父节点...一直到场景下面的根节点全都是激活状态。
而官方文档里的说的returns active GameObjects,其实是只能查找并返回activeInHierarchy为true的节点。也就是如果查找的节点,其任何祖先节点都没有隐藏,才能返回它们,否则只会返回null。
第二个关键点就是Unity场景中的多个GameObject可以有共同的名字,但是GameObject.Find只会返回一个,那么这种情况下查找的规则是怎样的呢?
这就和两个同名GameObject在Hierarchy的顺序有关了,无论同名的GameObject是同级节点还是互相嵌套的节点,在视图中顺序靠上者会被优先选出。比如下图,图例中两个名为A的GameObject,上面的那个会被查找函数作为结果返回。
同名查找的场景测试用例
第三个关键点就是name的具体含义。name参数可以是对查找路径中一部分层级关系的描述,也可以是全部的。
查找C的场景测试用例
比如我们要在上图的场景里查找名为C的GameObject,以下三种Find方法中的name实参都是可以的:
GameObject c0 = GameObject.Find("A/B/C");
GameObject c1 = GameObject.Find("B/C");
GameObject c2 = GameObject.Find("C");
只要name所描述的层级确实存在即可。
最后就是name中用于对层级进行描述的特殊符号’/’,在GameObject的name中其实也可以有这个符号,那查找带有这个符号的name时,由于Find的行为只会把’/’符号解释为分割符而非名字的一部分,所以这种情况是无法查找的,只能返回null了。
1.2 Transform.Find
public Transform Find(string n);
Transform类下公有函数,Unity文档对此函数的描述如下:
Finds a child by name n and returns it.
If no child with name n can be found, null is returned. If n contains a '/' character it will access the Transform in the hierarchy like a path name.
Note: Find does not perform a recursive descend down a Transform hierarchy.
Note: Find can find transform of disabled GameObject.
所以这个函数的功能是基于某个对象的Transform组件,在这个组件的下面进行子组件的查找。
Transform是Unity的GameObject-组件系统中一个特殊的组件以致于直接成为了GameObject类型的字段。Transform组件不可重复、不可移除,除了位置大小缩放,这个组件还持有父子关系有关的数据。
这里Transform.Find和GameObject.Find行为的主要区别是:
- 不支持递归查找,参数n是调用对象下的绝对的路径。
- 无论[1]或者查找的目标对象或者它们之间的任何对象有任何对象处于隐藏(非Active)状态,都可以进行查找。
Transform.Find的其他查找行为和GameObject.Find类似。
2. 辨析GameObject.GetComponent系列接口
当查找到对象之后,一般还需要查找对象自身的组件和子对象的组件。Unity提供了一些接口来根据对象来查询和对象有关的组件,下面是几个接口的函数原型:
public Component GetComponent(Type type);
public Component GetComponentInChildren(Type t, bool includeInactive);
public Component GetComponentInParent(Type t, bool includeInactive);
当然,这些接口也有其他的变体,这里只关注这三个常用的接口,因为查找行为和GameObject.Find有些类似,这里就一并总结了。
GetComponent函数只查找自身拥有的组件,如果查找不到会直接返回null。GetComponentInChildren函数和GetComponentInParent仍然先查找自身有无组件,如果没有分别向父级/子级进行查找。
Unity中GameObject的父子关系是一对多的,一个父节点可以有多个子节点。这时候GetComponentInChildren函数和GetComponentInParent的行为是深度遍历的,从离当前调用的GameObject最近的父/子节点开始遍历每个GameObject是否含有目标组件,一旦含有,就会把组件进行返回。
3. 一种最佳实践的思路
Unity的GameObject.Find接口无法查找非Active节点,Transform.Find接口不支持“相对路径”式的模糊匹配,两者都不支持对含有’/’符号的GameObject查找。通常在工程上,我们并不直接使用这两个需要仔细辨析功能,并且不适用于所有条件的接口来查找对象,而是在研究其行为后,自己封装一个统一的接口替代它们。
Unity提供了获取Active场景下,所有根GameObject的函数:
SceneManager.GetActiveScene().GetRootGameObjects()
我们可以把用此函数先获取到所有根节点,然后也同样进行递归式深度遍历。这样无论GameObject是否Active都可以查询到,同时自己维护匹配的路径数组,来模糊匹配效果。
对参数的处理上,我们可以把参数设置为string[]或者List类型来规避分割符号的存在、或者对分割符号进行转义、也可以让函数支持不同的分割符号来规避路径解析的问题。
参考
- ^网络上也有一些博文说需要调用函数对象是Active的才能查找。但是本文在2021.2.0环境下对查找行为进行了较为全面的测试,并没有发现这种情况。推测之前的版本,查找行为可能确如其他博文所说。但是最新的版本里,查找行为已经改变了。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|