Поиск…


Пул объектов

Иногда, когда вы делаете игру, вам нужно создавать и уничтожать много объектов одного и того же типа снова и снова. Вы можете просто сделать это, сделав сборку и инстанцируйте / уничтожьте это, когда вам нужно, однако, это неэффективно и может замедлить игру.

Один из способов обойти эту проблему - объединение объектов. В основном это означает, что у вас есть пул (с или без ограничения количества) объектов, которые вы собираетесь использовать повторно, когда это возможно, чтобы предотвратить ненужное создание или уничтожение.

Ниже приведен пример простого пула объектов

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 (иначе мы будем создавать бесконечно).

Это просто простой цикл, создающий новые объекты и помещая их в пул. Следует обратить внимание на то, что мы установили все экземпляры в неактивные. Таким образом, они еще не видны в игре.

Далее, есть функция 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 функция выглядит так же , как и собственное единство по Instantiate функция, кроме сборной уже указана выше в качестве члена класса.

Первый шаг функции Instantiate проверяет, есть ли в пуле неактивный объект прямо сейчас. Это означает, что мы можем повторно использовать этот объект и вернуть его запрашивающему. Если в пуле есть неактивный объект, мы устанавливаем позицию и поворот, устанавливаем его активным (иначе его можно было бы повторно использовать случайно, если вы забыли его активировать) и вернуть его запрашивающему.

Второй шаг возможен только в том случае, если в пуле нет неактивных элементов, и пул может расти по сравнению с начальной суммой. Все просто: добавляется другой экземпляр сборника и добавляется в пул. Разрешение роста пула помогает вам иметь нужное количество объектов в пуле.

Третий «шаг» происходит только в том случае, если в пуле нет неактивных элементов, и пул не может расти. Когда это произойдет, запросчик получит нулевой объект GameObject, что означает, что ничего не было доступно и должно быть обработано должным образом, чтобы предотвратить NullReferenceExceptions .

Важный!

Чтобы ваши предметы возвращались в пул, вы не должны уничтожать игровые объекты. Единственное, что вам нужно сделать, это установить их в неактивные и сделать их доступными для повторного использования через пул.

Простой пул объектов

Ниже приведен пример пула объектов, который позволяет арендовать и возвращать заданный тип объекта. Чтобы создать пул объектов, необходим Func для функции create и Action для уничтожения объекта, чтобы предоставить пользователю гибкость. При запросе объекта, когда пул пуст, будет создан новый объект и при запросе, когда пул имеет объекты, тогда объекты удаляются из пула и возвращаются.

Пул объектов

public class ResourcePool<T> where T : class
{
    private readonly List<T> objectPool = new List<T>();
    private readonly Action<T> cleanUpAction;
    private readonly Func<T> createAction;

    public ResourcePool(Action<T> cleanUpAction, Func<T> createAction)
    {
        this.cleanUpAction = cleanUpAction;
        this.createAction = createAction;
    }

    public void Return(T resource)
    {
        this.objectPool.Add(resource);
    }

    private void PurgeSingleResource()
    {
        var resource = this.Rent();
        this.cleanUpAction(resource);
    }

    public void TrimResourcesBy(int count)
    {
        count = Math.Min(count, this.objectPool.Count);
        for (int i = 0; i < count; i++)
        {
            this.PurgeSingleResource();
        }
    }

    public T Rent()
    {
        int count = this.objectPool.Count;
        if (count == 0)
        {
            Debug.Log("Creating new object.");
            return this.createAction();
        }
        else
        {
            Debug.Log("Retrieving existing object.");
            T resource = this.objectPool[count-1];
            this.objectPool.RemoveAt(count-1);
            return resource;
        }
    }
}

Пример использования

public class Test : MonoBehaviour
{
    private ResourcePool<GameObject> objectPool;

    [SerializeField]
    private GameObject enemyPrefab;

    void Start()
    {
        this.objectPool = new ResourcePool<GameObject>(Destroy,() => Instantiate(this.enemyPrefab) );
    }

    void Update()
    {
        // To get existing object or create new from pool
        var newEnemy = this.objectPool.Rent();
        // To return object to pool
        this.objectPool.Return(newEnemy);
        // In this example the message 'Creating new object' should only be seen on the frame call
        // after that the same object in the pool will be returned.
    }
}

Еще один простой пул объектов

Другой пример: Оружие, которое стреляет из Пули.

Оружие действует как пул объектов для создаваемых им Пули.

public class Weapon : MonoBehaviour {
    
    // The Bullet prefab that the Weapon will create
    public Bullet bulletPrefab;

    // This List is our object pool, which starts out empty
    private List<Bullet> availableBullets = new List<Bullet>();

    // The Transform that will act as the Bullet starting position
    public Transform bulletInstantiationPoint;

    // To spawn a new Bullet, this method either grabs an available Bullet from the pool,
    // otherwise Instantiates a new Bullet
    public Bullet CreateBullet () {
        Bullet newBullet = null;

        // If a Bullet is available in the pool, take the first one and make it active
        if (availableBullets.Count > 0) {
            newBullet = availableBullets[availableBullets.Count - 1];

            // Remove the Bullet from the pool
            availableBullets.RemoveAt(availableBullets.Count - 1);

            // Set the Bullet's position and make its GameObject active
            newBullet.transform.position = bulletInstantiationPoint.position;
            newBullet.gameObject.SetActive(true);
        }
        // If no Bullets are available in the pool, Instantiate a new Bullet
        else {
            newBullet newObject = Instantiate(bulletPrefab, bulletInstantiationPoint.position, Quaternion.identity);

            // Set the Bullet's Weapon so we know which pool to return to later on
            newBullet.weapon = this;
        }
        
        return newBullet;
    }

}

public class Bullet : MonoBehaviour {
    
    public Weapon weapon;

    // When Bullet collides with something, rather than Destroying it, we return it to the pool
    public void ReturnToPool () {
        // Add Bullet to the pool
        weapon.availableBullets.Add(this);

        // Disable the Bullet's GameObject so it's hidden from view
        gameObject.SetActive(false);
    }

}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow