Введение
Привет, начинающие разработчики игр! 🚀
Сегодня мы углубимся в мощную технику оптимизации игры — объединение объектов. Это жемчужина оптимизации, которую должен иметь в своем арсенале каждый разработчик игр. Представьте себе сокращение накладных расходов на постоянное создание и уничтожение игровых объектов, а вместо этого их «переработку». Звучит круто, правда? Давайте разберем это, используя простой и лаконичный пример кода!
Почему объединение объектов? 🤔
Каждый раз, когда вы создаете и уничтожаете объекты, сборщик мусора усердно работает, что может вызвать заметные сбои в вашей игре. Объединение объектов помогает избежать этого за счет повторного использования объектов вместо их частого создания и уничтожения.
Понимание реализации класса ObjectPoolManager
.
//Your prefab to be pooled to memory. [SerializeField] private GameObject _prefab; //Total count of the objects to be generated in the pool. [SerializeField] private int _objectPoolCount; //If checked true, all objects will stay under this gameobject in hierarchy. [SerializeField] private bool _parentToThisObject = true; //A queue to hold/store all pooled objects. private Queue<GameObject> _objectPoolQueue = new Queue<GameObject>();
_prefab — это игровой объект, который мы собираемся объединить. Думайте об этом как о пуле в стрелялке или повторно используемом эффекте частиц.
_objectPoolCount — определяет, сколько объектов мы хотим «предварительно создать» и иметь готовых к работе.
_objectPoolQueue — это очередь для хранения наших объектов в пуле. Мы используем очередь, потому что это быстро и эффективно для нашего варианта использования.
Вот где начинается волшебство. В методе InitializeObjectPool()
мы создаем наши объекты и держим их готовыми к действию.
//Object Pool Logic private void InitializeObjectPool() { //Iterating till the total count. for (int i = 0; i < _objectPoolCount; i++) { GameObject tempGO = Instantiate(_prefab); //Enqueuing the generated object to the memory. _objectPoolQueue.Enqueue(tempGO); if (_parentToThisObject) tempGO.transform.SetParent(this.transform); //Setting name for the object. tempGO.SetActive(false); } }
К концу этого метода у нас будет _objectPoolCount
количество игровых объектов, все деактивированные и ожидающие в очереди.
Получение и возврат объектов
Хотите игровой объект? Задайте метод GetObject()
. Готово? Передайте его методу ReturnObject()
.
Когда вам нужен объект из пула, получите его, как показано ниже.
Предположим, вы объединили игровые объекты в пул в памяти.
GameObject bullet = ObjectPoolManager.Instance.GetObject();
Давайте посмотрим на реализацию метода GetObject().
public GameObject GetObject() { if (_objectPoolQueue.Count > 0) { //Dequeue an object from the queue and allow to be used in the game. GameObject obj = _objectPoolQueue.Dequeue(); obj.SetActive(true); return obj; } else { //Generate a new object and return if none of the objects are available in the memory. //This is an edge case. GameObject obj = Instantiate(_prefab); return obj; } }
Функция GetObject() занимает центральное место в системе объединения объектов. Его основная цель — предоставить игровой объект — либо путем повторного использования ранее созданного объекта, либо путем создания нового.
При запросе объекта:
- Сначала функция проверяет, есть ли в очереди уже доступные объекты.
- Если в очереди есть объект, он удаляется из очереди, активируется (чтобы он был виден в игровой сцене), а затем возвращается вызывающему объекту. Этот процесс повторно использует объекты, которые были созданы ранее, но в настоящее время не используются.
- Однако, если очередь пуста (т. е. все объекты в настоящее время используются), новый игровой объект создается из префаба и возвращается вызывающей стороне. Этот сценарий гарантирует, что система останется гибкой, обслуживая ситуации, когда спрос превышает первоначальный размер пула.
Если ваш объект завершил свое использование в игре, вы можете вернуть его обратно в очередь или в пул объектов, как показано ниже.
ObjectPoolManager.Instance.ReturnObject(bullet);
Давайте кратко рассмотрим ReturnObject(), чтобы увидеть, что происходит, когда вы возвращаете объект обратно в пул.
//Use this method to return an object back to the memory after the use. public void ReturnObject(GameObject obj) { if (obj != null) { _objectPoolQueue.Enqueue(obj); obj.SetActive(false); } }
По сути, метод просто помещает игровой объект обратно в очередь и деактивирует его в иерархии для следующего использования.
Заключение по объединению объектов с помощью очередей и списков
Объединение объектов выделяется как одна из жизненно важных стратегий в разработке игр для оптимизации производительности. Он устраняет повторяющиеся накладные расходы на создание экземпляров и уничтожение путем повторного использования объектов. Однако выбор структуры данных может повлиять на эффективность и простоту механизма объединения.
Преимущества использования очереди:
- «Первым пришел — первым обслужен» (FIFO). По своей природе очередь работает по принципу FIFO. Это гарантирует, что объекты, которые были первыми деактивированы (или возвращены в пул), будут повторно активированы первыми. Это может привести к лучшей локальности памяти, потенциально улучшая когерентность кэша и производительность.
- Ясное намерение: когда вы используете очередь для объединения объектов, намерение ясно. Вы перерабатываете объекты в том порядке, в котором они были возвращены, что делает код семантически более понятным для разработчиков.
- Эффективность: такие операции, как
Enqueue
иDequeue
в очереди, обычно выполняются за O(1), что обеспечивает постоянную скорость извлечения и возврата объектов в пул.
Недостатки использования списка вместо очереди для объединения объектов.
- Накладные расходы при произвольном доступе. При использовании списка, если вы пытаетесь получить первый неактивный объект, вам может потребоваться пройтись по списку, что в худшем случае может занять O(n) времени. Хотя это может быть незначительным для небольших списков, по мере роста списка накладные расходы могут стать проблемой.
- Неоднозначность. Использование списка не обеспечивает четкого шаблона повторного использования объектов. Разработчик потенциально может захватить объект из середины списка, что приведет к неоднородным шаблонам использования и усложнит прогнозирование кода.
- Управление памятью. Хотя списки являются динамическими и могут увеличиваться или уменьшаться, такое изменение размера может привести к дополнительным операциям с памятью, что может повлиять на производительность. Кроме того, удаление объекта из середины списка может привести к смещению элементов, что может потребовать значительных вычислительных ресурсов.
Всего несколько строк, и ObjectPoolManager значительно повысит производительность вашей игры. Эту технику должен знать любой разработчик, новичок или профессионал. Итак, начните объединять и дайте своей игре плавность, которой она заслуживает!
Помните, что в разработке игр ключевым фактором является эффективность. Освойте объединение объектов, и вы откроете одну из многих дверей в высокопроизводительные игры! 🎮🔥
Удачного кодирования! 💻🌟
Вы можете найти мой репозиторий Git здесь: https://github.com/vivekrajgopal/Object_Pooling_Sample