【知识链】Unity -> 脚本系统 -> 访问游戏对象 -> 按类型访问游戏对象
摘要:本文介绍了Unity中按类型查找游戏对象(GameObject)的五种方法,并提出了使用这些方法的最佳实践。
一、访问游戏对象的方法
在Unity中,对游戏对象进行操作前,首要步骤是确保能准确查找到目标游戏对象。Unity提供了多种脚本访问游戏对象的方法,以满足不同需求:
(1)通过名称访问:使用Find(string name)
方法,通过指定游戏对象的名称来查找并获取该对象。
(2)通过标签访问:FindGameObjectsWithTag(string tag)
和FindWithTag(string tag)
方法允许开发者通过标签来查找具有相同标签的游戏对象集合或单个对象。
(3)通过类型访问:FindObjectByType(Type type)
方法允许开发者按照游戏对象的类型进行查找。值得注意的是,按名称和按标签的访问方法属于GameObject
类,而按类型访问的方法则属于更广泛的Object
类。由于GameObject
是Object
的子类,因此也可以通过类型访问游戏对象。
二、如何按类型查找游戏对象
按类型查找游戏对象作为Object类的一个重要方法,其核心功能在于实现针对特定类型的游戏对象的检索。该方法主要提供了五个不同的重载方法,以便用户能够根据不同的需求进行精确查找。具体方法如以下表格所示:
方法 | 说明 | 重载 |
---|---|---|
FindAnyObjectByType | 查找任意按指定类型匹配的已加载的游戏对象(未排序时返回第一个匹配) | public static Object FindAnyObjectByType(Type type, FindObjectsInactive findObjectsInactive); |
FindFirstObjectByType | 查找第一个按类型匹配的已加载的游戏对象(排序后返回第一个匹配对象) | public static Object FindFirstObjectByType(Type type, FindObjectsInactive findObjectsInactive); |
FindObjectsByType | 查找所有按类型匹配的已加载的游戏对象(可以选择是否按InstanceID排序) | public static Object[] FindObjectsByType(Type type, FindObjectsInactive findObjectsInactive, FindObjectsSortMode sortMode); |
FindObjectOfType | 查找第一个按类型匹配的已加载的游戏对象(默认按InstanceID排序) | public static Object FindObjectOfType(Type type, bool includeInactive); |
FindObjectsOfType | 查找所有按类型匹配的已加载的游戏对象(默认按InstanceID排序) | public static Object[] FindObjectsOfType(Type type, bool includeInactive); |
在正式介绍各个方法的使用细节之前,我们首先需要明确每个方法中涉及的Type参数的含义。在此处,Type特指Object的类型定义。请注意,在后续的最佳实践部分,我们会明确指出,上述方法并不适用于查找资产类(Assets)资源。此外,本文末尾还将对Object的类别进行详细阐述,以便读者更好地理解与应用。
三、五种查找方法的使用
这五种方法均具备四种重载形式,现以FindAnyObjectByType方法为例进行详尽阐述,其余四种方法的使用方式与之相类似。
1. FindAnyObjectByType
重载方法:
public static T FindAnyObjectByType();
public static T FindAnyObjectByType(FindObjectsInactive findObjectsInactive);
public static Object FindAnyObjectByType(Type type);
public static Object FindAnyObjectByType(Type type, FindObjectsInactive findObjectsInactive);
(1)四种重载,可以分为两类,一是泛型方法,另外一种是指定对象类型的方法。
(2)从参数上看,两个参数
Type:要查找的对象的类型
FindObjectsInactive:是否包含附加到非活动游戏对象的组件。如果不指定此参数,则此函数不会在结果中包含非活动对象。
那如何选择在什么场合使用哪种重载方法呢?
如果你在编译时就知道要查找的类型,并且希望代码简洁,可以使用泛型方法 FindAnyObjectByType。
如果类型是在运行时动态确定的,或者你需要在不确定类型的情况下查找对象,可以使用 FindAnyObjectByType(Type, FindObjectsInactive)。
示例代码1 - 使用泛型方法
using UnityEngine;
public class FindAnyObjectByTypeWithInactiveExample : MonoBehaviour {
void Start() {
// 查找第一个匹配类型为 PlayerController 的对象,包括非活动对象
PlayerController playerController = Object.FindAnyObjectByType<PlayerController>(FindObjectsInactive.Include);
if (playerController != null) {
Debug.Log("Found a PlayerController object.");
} else {
Debug.Log("No PlayerController object found.");
}
}
}
示例代码2 - 使用常规方法
using UnityEngine;
public class FindAnyObjectByTypeWithInactiveExample : MonoBehaviour {
void Start() {
// 查找第一个匹配类型为 PlayerController 的对象,包括非活动对象
PlayerController playerController = (PlayerController)Object.FindAnyObjectByType(typeof(PlayerController), FindObjectsInactive.Include);
if (playerController != null) {
Debug.Log("Found a PlayerController object.");
} else {
Debug.Log("No PlayerController object found.");
}
}
}
2. FindFirstObjectByType
此方法用于查找第一个匹配类型的对象,包括非活动对象。
public static Object FindFirstObjectByType(Type type, FindObjectsInactive findObjectsInactive);
findObjectsInactive: 是否包含附加到非活动游戏对象的组件。如果不指定此参数,则此函数不会在结果中包含非活动对象。
2. FindObjectsByType
此方法用于查找所有匹配类型的对象,可以选择是否包含非活动对象,并对结果进行排序。
public static T[] FindObjectsByType(FindObjectsInactive findObjectsInactive, FindObjectsSortMode sortMode);
sortMode: 是否以及如何对返回的数组进行排序。不对数组进行排序可使此函数的运行速度显著加快。
FindObjectsSortMode有两个值
(1)InstanceID:对结果按InstanceID升序排列(InstanceID是Unity内部对每个对象指定的一个整数ID,只存在于Unity运行时,不能用于游戏逻辑)
(2)None:不对结果进行排序
4. FindObjectOfType
此方法用于查找一个类型的对象,默认查找活动对象,但可以选择包含非活动对象。
public static Object FindObjectOfType(Type type, bool includeInactive);
includeInactive:是否包含非活动游戏对象。如果不指定此参数,则此函数不会在结果中包含非活动对象。
5. FindObjectsOfType
此方法用于查找所有匹配类型的对象,可以选择是否包含非活动对象。
public static Object[] FindObjectsOfType(Type type, bool includeInactive);
经过对比分析,FindObjectByType与FindObjectOfType的主要差异在于前者提供了是否进行排序的选项,而后者则默认对查找结果进行了排序处理。
值得注意的是,自2023.1版本起,FindObjectOfType和FindObjectsOfType这两个方法已被弃用。因此,在学习和使用过程中,我们应主要关注前三个方法。
弃用这两个方法的原因在于,它们对查找结果进行排序的过程消耗了大量时间,占据了整个查找过程用时的90%以上。为了提高效率,开发团队决定废除这两个方法,并推荐使用FindObjectByType等三个方法,由开发者自行根据需求决定是否对查找结果进行排序。如需更多信息,请查阅相关文档。
翻译一下就是:
使用Volvo测试项目和 Gigaya 对进入/退出游戏模式进行分析表明,在 FindObjectsOfType() 中花费的时间中约有 95% 用于按 InstanceID 对数组进行排序,尽管在几乎所有情况下这都被认为是不必要的。
在 Volvo 项目 (2022.1) 中,在单次进入/退出播放模式循环中,Object::FindObjectsOfType() 花费了 203 毫秒,其中 190 毫秒用于排序 (93.6%)
在 Gigaya (2021.3) 中,在单次进入/退出播放模式循环中,Object::FindObjectsOfType() 花费了 496 毫秒,其中 461 毫秒用于排序 (92.9%)
在 #devs-scripting 中,关于此问题的可能解决方案进行了长时间的讨论 (https://unity.slack.com/archives/C06TPSM32/p1651840563109579),现在达成的共识是弃用 FindObjectsOfType() 并将其替换为 FindObjectsByType()
让用户选择是否执行排序
请注意,让 API 更新程序自动将 FindObjectsOfType() 转换为FindObjectsByType(FindObjectsSortMode.InstanceID),因为我们确实希望用户根据具体情况评估其使用情况,并且只在必要时选择
排序以最大化性能增益
* 计划是:
2023.1 :
* FindObjectsOfType() 已过时(警告),将用户引导至 FindObjectsByType
* FindObjectOfType() 已过时(警告),将用户引导至 FindFirstObjectByType 和 FindAnyObjectByType
2023.2
* FindObjectsOfType() 已过时(错误),将用户引导至 FindObjectsByType
* FindObjectOfType() 已过时(错误),将用户引导至 FindFirstObjectByType 和 FindAnyObjectByType
2024.2
* FindObjectsOfType() 已删除
* FindObjectOfType() 已删除
这项工作已在 https://jira.unity3d.com/browse/COPT-854 中记录
四、最佳实践
当然在使用上述方法过程中还有以下需要注意的。
(1)以上方法都不能查看资源(例如mesh, texture, prefab),或者未激活游戏对象。如果对象设置了HideFlags.Dontsave标签,这个对象也不会被返回。可以使用 Resources.FindObjectsOfTypeAll 来避免以上的限制。
(2)在编辑器中,默认情况下会搜索场景视图。如果要在预制件阶段查找对象,请参阅StageUtility API。
(3)这里面速度最快的是FindAnyObjectByType,因为不会对结果进行排序。
五、学以致用 - 随堂测验
测验1:寻找任意一个指定类型的游戏对象
场景描述:
你正在开发一个Unity项目,需要在游戏启动时找到场景中的任意一个Light组件,并根据它的属性设置其他组件的初始状态。
问题:在这种情况下,你应该使用哪种方法来查找Light组件?
A. FindAnyObjectByType()
B. FindFirstObjectByType()
C. FindObjectsByType()
测验2:寻找场景中所有的敌人对象
场景描述:
你正在开发一个塔防游戏,需要在每一帧更新时遍历所有的敌人对象,以便调整它们的行为和状态。敌人对象都包含一个Enemy脚本组件。
问题:在这种情况下,你应该使用哪种方法来查找所有Enemy组件?
A. FindAnyObjectByType()
B. FindFirstObjectByType()
C. FindObjectsByType()
测验3:寻找第一个活动的玩家对象
场景描述:
你正在开发一个多人游戏,需要在玩家加入游戏时找到第一个活动的PlayerController组件,以便初始化该玩家的相关数据。
问题:在这种情况下,你应该使用哪种方法来查找第一个活动的PlayerController组件?
A. FindAnyObjectByType()
B. FindFirstObjectByType()
C. FindObjectsByType()
六、福利:给你介绍个对象:)
(一)关于类型的说明
本文中所有方法参数中的Type指的是游戏对象的类型。在Unity中,Object类是所有游戏对象及组件的基类。从Object派生出来的所有类如下图所示。我们可以看出,绝大多数的类都属于Component(组件),这也和Unity组件化的架构设计是呼应的。
(二)介绍一下游戏对象
本文中提到按类型查找游戏对象的方法是不能用来查找游戏资产的。下面来介绍一下Unity中游戏对象(Object)和游戏资产(Assets)的区别与联系。
简单来说,游戏对象是游戏世界中的实体,动态存在并且可以在运行时操作,而游戏资产是静态资源,定义了游戏对象的属性和行为。游戏对象可以在Inspector中编辑属性,而游戏资产则是通过Project进行管理。
对于游戏对象和资产的关系,我们可以以搭建舞台为例:
- 舞台上的道具、灯光、布景、音响等都是资产:这些道具、灯光、布景、音响等存储在仓库里,定义了舞台上将要使用的所有资源。
- 当这些元素被使用在舞台上时,它们就成了游戏对象:这些元素一旦被放置在舞台上,并开始演出,它们就变成了具体的对象,成为演出的一部分。
二者间联系
- 实例化:就像从仓库中拿出道具、灯光等并布置到舞台上,游戏资产也可以被实例化为游戏对象,并放置在游戏场景中。
- 引用:道具的具体外观、灯光的颜色和强度等属性都由这些资产决定,正如游戏对象引用游戏资产来定义其属性和行为。
【学以致用】随堂测验的正确答案:(1)A(2)C(3)B