对象池

有时当你制作游戏时,你需要一遍又一遍地创建和销毁大量相同类型的对象。你可以通过制作预制件并在需要时实例化/销毁它来执行此操作,但是,执行此操作效率低下并且可能会降低你的游戏速度。

解决此问题的一种方法是对象池。基本上这意味着你有一个池(有或没有数量的限制)你将要重用的对象,只要你可以防止不必要的实例化或破坏。

下面是一个简单对象池的示例

public class ObjectPool : MonoBehaviour 
{
    public GameObject prefab;
    public int amount = 0;
    public bool populateOnStart = true;
    public bool growOverAmount = true;

    private List<GameObject> pool = new List<GameObject>();

    void Start() 
    {
        if (populateOnStart && prefab != null && amount > 0) 
        {
            for (int i = 0; i < amount; i++) 
            {
                var instance = Instantiate(Prefab);
                instance.SetActive(false);
                pool.Add(instance);
            }
        }
    }

    public GameObject Instantiate (Vector3 position, Quaternion rotation) 
    {
        foreach (var item in pool) 
        {
            if (!item.activeInHierarchy) 
            {
                item.transform.position = position;
                item.transform.rotation = rotation;
                item.SetActive( true );
                return item;
            }
        }

        if (growOverAmount) 
        {
            var instance = (GameObject)Instantiate(prefab, position, rotation);
            pool.Add(instance);
            return instance;
        }

        return null;
    }
}

我们先来看看变量

public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;

private List<GameObject> pool = new List<GameObject>();
  • GameObject prefab:这是对象池用于将新对象实例化到池中的预制件。
  • int amount:这是池中可以包含的最大项目数。如果要实例化另一个项目并且池已达到其限制,则将使用池中的另一个项目。
  • bool populateOnStart:你可以选择在开始时填充池。这样做会使预制件的实例填满游泳池,这样当你第一次调用 Instantiate 时,你将获得一个已经存在的对象
  • bool growOverAmount:将此设置为 true 允许池在超过特定时间范围内请求的数量时增长。你并不总是能够准确预测要放入池中的项目数量,因此这将在需要时向池中添加更多内容。
  • List<GameObject> pool:这是池,存储所有实例化/销毁对象的地方。

现在让我们看看 Start 功能

void Start() 
{
    if (populateOnStart && prefab != null && amount > 0) 
    {
        for (int i = 0; i < amount; i++) 
        {
            var instance = Instantiate(Prefab);
            instance.SetActive(false);
            pool.Add(instance);
        }
    }
}

在启动函数中,我们检查是否应该在开始时填充列表,如果已经设置了 prefab 并且金额大于 0(否则我们将无限制地创建)。

这只是一个简单的 for 循环实例化新对象并将它们放入池中。要注意的一件事是我们将所有实例设置为非活动状态。这样他们在游戏中就看不到了。

接下来,有 Instantiate 函数,这是大多数魔法发生的地方

public GameObject Instantiate (Vector3 position, Quaternion rotation) 
{
    foreach (var item in pool) 
    {
        if (!item.activeInHierarchy) 
        {
            item.transform.position = position;
            item.transform.rotation = rotation;
            item.SetActive(true);
            return item;
        }
    }

    if (growOverAmount) 
    {
        var instance = (GameObject)Instantiate(prefab, position, rotation);
        pool.Add(instance);
        return instance;
    }

    return null;
}

Instantiate 功能看起来就像 Unity 自己的 Instantiate 功能,除了预制件已作为类成员提供。

Instantiate 函数的第一步是检查池中是否存在非活动对象。这意味着我们可以重用该对象并将其返回给请求者。如果池中有一个非活动对象,我们设置位置和旋转,将其设置为活动(否则如果你忘记激活它可能会被意外重用)并将其返回给请求者。

第二步只有在池中没有非活动项且允许池增长超过初始量时才会发生。发生的事情很简单:创建预制件的另一个实例并将其添加到池中。允许池的增长可以帮助你在池中拥有适量的对象。

第三个只如果在池中没有不活动的项目情况和池让它生长。当发生这种情况时,请求者将收到一个空 GameObject,这意味着什么都没有,应该正确处理以防止 NullReferenceExceptions

重要!

为了确保你的项目得到放回池中,你应该破坏游戏的对象。你唯一需要做的就是将它们设置为非活动状态,这样可以通过池重新使用它们。