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;
    }

我已经尝试过了,函数工作得很好,但我想知道这种方法是否有任何主要的缺点,或者是否有其他更好的方法来完成相同的任务,可能使用我不知道的现有方法或以其他方式创建方法。

Unity:获取或添加组件的通用方法

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附加。实际上,这是一个很大的限制。如果您真的试图将组件彼此分离,您将遇到必须添加和删除组件的情况。

不幸的是,没有内置方法允许"Add或"获取组件。虽然这应该不是很难实现:
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函数中这样做,就不会有任何性能差异。