Unity:获取或添加组件的通用方法
本文关键字:方法 组件 添加 获取 Unity | 更新日期: 2023-09-27 18:01:43
每次我在Unity中创建一个新脚本时,我总是会对脚本所依赖的组件进行一堆检查,比如:
SpriteRenderer sr = gameObject.GetComponent<SpriteRenderer>();
if(sr == null)
sr = gameObject.AddComponent<SpriteRenderer>() as SpriteRenderer;
所以我决定做一个通用的方法来处理任何组件,我想出了下面的代码,我只是把它添加到我自己的新脚本。
T GetOrAddComponent<T>() where T : Component
{
T component = gameObject.GetComponent<T>();
if (component == null)
component = gameObject.AddComponent<T>() as T;
return component;
}
我已经尝试过了,函数工作得很好,但我想知道这种方法是否有任何主要的缺点,或者是否有其他更好的方法来完成相同的任务,可能使用我不知道的现有方法或以其他方式创建方法。
GetComponent方法实际上给系统带来了相当大的负担。如果你是面向手机平台开发游戏,那么过多的元素可能会导致加载时间明显滞后。
我个人使用RequireComponent方法。然后,脚本将在组件添加到GameObject时自动添加组件。然后,我将手动将组件的名称拖到检查器中脚本对象的字段中。
的例子:
using UnityEngine;
// PlayerScript requires the GameObject to have a RigidBody component
[RequireComponent (typeof (RigidBody))]
public class PlayerScript : MonoBehavior {
[SerializeField]
private RigidBody rb;
...
}
首先我建议你使用"RequireComponent":
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(AnotherComponent))]
public class NetworkService : MonoBehaviour
{
private SpriteRenderer _sRenderer;
//strong way to never get a null reference.
#if UNITY_EDITOR
private void OnValidate()
{
if (null == _sRenderer)
_sRenderer = GetComponent<_sRenderer>();
}
#endif
"OnValidate"方法将在每次修改编辑器中的参数值或加载脚本/组件时执行。
public static void GetOrAddComponent<T>(this GameObject model, Action<T> onSucess) where T: Component {
onSucess(model.GetComponent<T>() ?? model.AddComponent<T> () as T);
}
在新版本的Unity (c# 8.0及以上版本)中,您可以简单地在一行中完成此操作。
[SerializeField] private SpriteRenderer spriteRenderer;
public SpriteRenderer GetSpriteRenderer => spriteRenderer ??= GetComponent<SpriteRenderer>() ?? gameObject.AddComponent<SpriteRenderer>();
上面的代码做了三件事:
- 如果getter属性不为空,则返回私有本地spriteRenderer组件。
- 如果spriteRenderer为null,则在返回之前通过调用GetComponent()继续分配值。
- 如果GetComponent()返回null,那么下一个操作将添加精灵渲染组件到游戏对象,并在返回之前分配给spriteRenderer。
下面是使用扩展方法的示例:
public static class MonoBehaviourGetOrAddComponent {
public static T GetOrAddComponent<T>(this GameObject self) where T : Component
{
T component = self.GetComponent<T>();
return component != null
? component : self.AddComponent<T>() as T;
}
}
这里的许多人都指出了[RequireComponent()]
属性,这是一个公平的观点,但我认为在某些情况下,它根本不够。如果要处理的对象只会发生一点点变化(如果根本不发生变化),则此属性非常有用。此外,该属性意味着特定组件应该始终与给定的Monobehavior附加。实际上,这是一个很大的限制。如果您真的试图将组件彼此分离,您将遇到必须添加和删除组件的情况。
public static T AddOrGetComponent<T>(this GameObject gameObject) where T : Component
{
return gameObject.TryGetComponent<T>(out var outComponent)
? outComponent
: gameObject.AddComponent<T>();
}
正如其他人指出的那样,GetComponent<T>()
是一个昂贵的操作,所以无论如何您都应该小心使用它。
如果您愿意,可以在一个if语句中完成所有操作:
SpriteRenderer sr;
if(gameObject.GetComponent<SpriteRenderer>())
sr = gameObject.GetComponent<SpriteRenderer>();
else
sr = gameObject.AddComponent<SpriteRenderer>() as SpriteRenderer;
您可能会两次调用GetComponent
,但如果您更喜欢代码结构,那么就这样吧。我个人认为你做这件事的方式是最好的。
为了更简洁,您可以直接返回组件,而不必将其存储在变量component
public T GetOrAddComponent<T>() where T : Component
{
if (gameObject.GetComponent<T>())
return gameObject.GetComponent<T>();
else
return gameObject.AddComponent<T>() as T;
}
只要你在场景加载或start
函数中这样做,就不会有任何性能差异。