Урок рассчитан на новичков, у профи я догадываюсь уже написаны свои супер пулы на все случаи жизни =)
Итак, вкратце - зачем же нужен пул и что это такое?
Пул нужен для оптимизации в тех играх, где часто создаются и уничтожаются какие-нибудь объекты. Например, пули или враги.
При создании нового объекта, программа пробегается по оперативной памяти, выискивая свободное местечко куда бы этот объект запихнуть. Это долго. А потом надо еще записать в найденное место байты этого объекта, запустить конструктор, всякие события в движке, происходящие при создании объекта, и так далее. Всё это жрёт большое количество процессорного времени и может приводить к тормозам. Особенно на слабых компьютерах и на телефонах.
При частом удалении объектов тоже могут быть лаги, связанные с работой сборщика мусора. Сборщик мусора обычно запускается когда приложению нужно больше памяти, чем оно уже занимало раньше. Сборщик пробегается по уже занятой памяти, и пытается определить, какие объекты уже не будут использоваться в игре, потому что ни одному другому объекту они больше не нужны. Это занимает очень много процессорного времени.
Вот так выглядит сборщик мусора в мобильной стрелялке без пулов:
Так чем же нам поможет пул?
Очень просто - вместо уничтожения, объекты будут только деактивироваться, и записываться в пул, оставаясь таким образом в оперативной памяти. А вместо создания новых объектов, будут браться уже созданные объекты из пула, и только активироваться!
Дальше опишу свою реализацию пула на юнити, универсальную и простую в использовании:
Для начала инициализация:
Метод PoolManager.init() надо будет дёрнуть в начале игры, передав в параметр трансформ того геймобжекта, который точно не будет уничтожаться. В этот геймобжект будут класться все деактивированные объекты, чтобы случайно не удалить их по ходу игры, вызвав Destroy() изначального родительского геймобжекта =)
Про словарь (Dictionary) будет дальше.
Функция получения объекта из пула:
В параметр PoolManager.getGameObjectFromPool() надо передать префаб, по которому создаётся объект.
Все операции доставания из пула или укладывания в него происходят по имени префаба.
Строчка if(!poolsDictionary.ContainsKey(prefab.name)) проверяет, был ли уже в пуле объект от этого префаба, и если нет, то далее создаётся хранилище (LinkedList) для объектов которые создаются по этому префабу.
Строчка if (poolsDictionary[prefab.name].Count > 0) проверяет, есть ли сейчас в хранилище деактивированные объекты от этого префаба. Если да, то объект берётся из хранилища, делается активным (SetActive()) и возвращается как результат работы функции.
Если же хранилище пустует, то создаётся новый объект через GameObject.Instantiate().
Ему присваивается имя, префаба, которое потом будет использоваться для возвращения в пул, и функция возвращает новый объект.
Функция возврата объекта в пул:
Тут всё просто, берётся имя объекта который мы хотим вернуть в пул, по этому имени в словаре находится хранилище, объект добавляется туда, переносится в безопасный parent и деактивируется.
Всё, пул готов!
Реализация очень простая, но у неё есть пара ограничений:
1) Нельзя делать префабы с одинаковыми названиями, лежащие в разных папках. В этом пуле они перемешаются потому что тут всё играется от названия префаба.
2) И соответственно нельзя динамически менять имя пулируемого геймобжекта, потому что оно должно быть как у префаба.
Но благодаря этим небольшим ограничениям, не надо возиться со всякими дополнительными компонентами типа IPoolable и т.д., мой пул очень прост в использовании. Просто вместо GameObject.Instantiate(prefab) вызываешь PoolManager.getFromPool(prefab), А вместо GameObject.Destroy(gameObject) PoolManager.putToPool(gameObject)
p.s. И конечно надо не забыть после взятия из пула проставить правильного родителя если это нужно, и прочие переменные вроде hp и так далее вернуть к исходным значениям.