В процессе разработки я столкнулся с необходимостью создания пула объектов. Прочитав эту и другие статьи, решил написать для своих нужд пул попроще с доступом к объекту по строке (названию префаба).
Итак, начнем. Пул состоит из четырех скриптов. Состояние вкл/выкл на объекте в пуле определяется его свойством Unity activeInHierarchy, чтобы не городить дополнительных переменных.
1. Pool Object
Компонент Pool Object должен находиться на любом объекте, используемом в пуле. Его основное предназначение — вернуть объект обратно в пул.
using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/PoolObject")] public class PoolObject : MonoBehaviour { #region Interface public void ReturnToPool () { gameObject.SetActive (false); } #endregion }
У класса один-единственный метод. На самом деле можно было бы обойтись и без него, но таким образом мы разделяем объекты, предназначенные для пула и нет (уничтожаемые обычным способом).
2. Object Pooling
Идем дальше. Класс Object Pooling — собственно сам пул, который выдает свободные объекты по требованию и создает новые при нехватке.
using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/ObjectPooling")] public class ObjectPooling { #region Data List<PoolObject> objects; Transform objectsParent; #endregion
Здесь objects — все объекты, содержащиеся в пуле, objectsParent используется только как их родитель в иерархии на сцене (чтобы не было простыни объектов).
Добавление происходит с помощью метода AddObject, принимающего образец, который нужно добавить и родителя в иерархии на сцене.
void AddObject(PoolObject sample, Transform objects_parent) { GameObject temp = GameObject.Instantiate(sample.gameObject); temp.name = sample.name; temp.transform.SetParent (objects_parent); objects.Add(temp.GetComponent<PoolObject> ()); if (temp.GetComponent<Animator> ()) temp.GetComponent<Animator> ().StartPlayback (); temp.SetActive(false); }
Создается Gameobject temp, ему присваивается имя образца, после чего он добавляется в наш List. Затем объект выключается до тех пор, пока его не «потребуют» снаружи.
Отдельно о строках:
if (temp.GetComponent<Animator> ()) temp.GetComponent<Animator> ().StartPlayback ();
Введены они были, т.к. при создании объекта без них аниматор не стартовал (update не успевало вызваться). В итоге, когда при старте сцены создавалось, например 100 пуль и сразу выключалось, при запросе 50 пуль, аниматор стартовал у всех одновременно и начинались проседания фпс (на многих объектах постоянно проигрывается анимация). Если в проекте не предполагается использование большого числа объектов с аниматорами, данный код не нужен.
Рассмотрим инициализацию:
public void Initialize (int count, PoolObject sample, Transform objects_parent) { objects = new List<PoolObject> (); //инициализируем List objectsParent = objects_parent; //инициализируем локальную переменную для последующего использования for (int i=0; i<count; i++) { AddObject(sample, objects_parent); //создаем объекты до указанного количества } }
Второй метод данного класса — GetObject(), возвращающий Gameobject:
public PoolObject GetObject () { for (int i=0; i<objects.Count; i++) { if (objects[i].gameObject.activeInHierarchy==false) { return objects[i]; } } AddObject(objects[0], objectsParent); return objects[objects.Count-1]; }
Логика проста — проходимся по листу, если какой-то из объектов в пуле выключен (т.е. свободен) — возвращаем его, иначе добавляем новый.
3. PoolManager
Следующий класс PoolManager управляет пулами различных объектов. Класс статический для упрощения доступа к объектам, т.е. не нужно создавать синглтоны, инстансы и прочее.
using UnityEngine; using System.Collections; using System.Collections.Generic; public static class PoolManager{ private static PoolPart[] pools; private static GameObject objectsParent; [System.Serializable] public struct PoolPart { public string name; //имя префаба public PoolObject prefab; //сам префаб, как образец public int count; //количество объектов при инициализации пула public ObjectPooling ferula; //сам пул }
Вся информация хранится в структуре PoolPart.
Инициализация производится массивом этих структур: (ferula, возможно не совсем удачное название, но позволяет не запутаться в куче pool-ов):
public static void Initialize(PoolPart[] newPools) { pools = newPools; //заполняем информацию objectsParent = new GameObject (); objectsParent.name = "Pool"; //создаем на сцене объект Pool, чтобы не заслонять иерархию for (int i=0; i<pools.Length; i++) { if(pools[i].prefab!=null) { pools[i].ferula = new ObjectPooling(); //создаем свой пул для каждого префаба pools[i].ferula.Initialize(pools[i].count, pools[i].prefab, objectsParent.transform); //инициализируем пул заданным количество объектов } } }
Второй метод данного статического класса — GetObject, аналог стандартного Instantiate, но по имени объекта. Он проверяет все существующие пулы, и если находит правильный — дергает его метод GetObject() у класса ObjectPooling:
public static GameObject GetObject (string name, Vector3 position, Quaternion rotation) { GameObject result = null; if (pools != null) { for (int i = 0; i < pools.Length; i++) { if (string.Compare (pools [i].name, name) == 0) { //если имя совпало с именем префаба пула result = pools[i].ferula.GetObject ().gameObject; //дергаем объект из пула result.transform.position = position; result.transform.rotation = rotation; result.SetActive (true); //выставляем координаты и активируем return result; } } } return result; //если такого объекта нет в пулах, вернет null }
4. PoolSetup
Однако необходимо редактировать объекты, предназначенные для использования в пуле, и их количество, в инспекторе Unity. Для этого придется написать класс-обертку, наследника MonoBehaviour, вешающегося на объекты:
using UnityEngine; using System.Collections; [AddComponentMenu("Pool/PoolSetup")] public class PoolSetup : MonoBehaviour {//обертка для управления статическим классом PoolManager #region Unity scene settings [SerializeField] private PoolManager.PoolPart[] pools; //структуры, где пользователь задает префаб для использования в пуле и инициализируемое количество #endregion #region Methods void OnValidate() { for (int i = 0; i < pools.Length; i++) { pools[i].name = pools[i].prefab.name; //присваиваем имена заранее, до инициализации } } void Awake() { Initialize (); } void Initialize () { PoolManager.Initialize(pools); //инициализируем менеджер пулов } #endregion }
Данный класс должен быть один на сцене, в противном случае один из них затрет пулы другого.
Использование
Теперь мы можем вызывать объекты из пула так:
Gameobject bullet = PoolManager.GetObject (bulletPrefab.name, shotPoint.position, myTransform.rotation);
Возвращаем:
GetComponent<PoolObject>.ReturnToPool ();
В результате пул работает и им достаточно просто пользоваться. Пара скринов:
Управление в редакторе:

Спавн пуль и кораблей:

Послесловие
Разумеется, у данной реализации множество недостатков. Перечислю основные:
1) Доступ по строке можно заменить доступом по, например, целочисленному ключу-идентификатору, что ускорило бы работу;
2) Нет обработки ошибок и исключений (методы просто вернут null), практически нет проверок;
3) Необходимость наличия на сцене по сути синглтона PoolSetup, хотя на него никто и не ссылается.
Полный код
using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/PoolObject")] public class PoolObject : MonoBehaviour { #region Interface public void ReturnToPool () { gameObject.SetActive (false); } #endregion }
using UnityEngine; using System.Collections; using System.Collections.Generic; [AddComponentMenu("Pool/ObjectPooling")] public class ObjectPooling { #region Data List<PoolObject> objects; Transform objectsParent; #endregion #region Interface public void Initialize (int count, PoolObject sample, Transform objects_parent) { objects = new List<PoolObject> (); objectsParent = objects_parent; for (int i=0; i<count; i++) { AddObject(sample, objects_parent); } } public PoolObject GetObject () { for (int i=0; i<objects.Count; i++) { if (objects[i].gameObject.activeInHierarchy==false) { return objects[i]; } } AddObject(objects[0], objectsParent); return objects[objects.Count-1]; } #endregion #region Methods void AddObject(PoolObject sample, Transform objects_parent) { GameObject temp; temp = GameObject.Instantiate(sample.gameObject); temp.name = sample.name; temp.transform.SetParent (objects_parent); objects.Add(temp.GetComponent<PoolObject> ()); if (temp.GetComponent<Animator> ()) temp.GetComponent<Animator> ().StartPlayback (); temp.SetActive(false); } #endregion }
using UnityEngine; using System.Collections; using System.Collections.Generic; public static class PoolManager{ private static PoolPart[] pools; private static GameObject objectsParent; [System.Serializable] public struct PoolPart { public string name; public PoolObject prefab; public int count; public ObjectPooling ferula; } public static void Initialize(PoolPart[] newPools) { pools = newPools; objectsParent = new GameObject (); objectsParent.name = "Pool"; for (int i=0; i<pools.Length; i++) { if(pools[i].prefab!=null) { pools[i].ferula = new ObjectPooling(); pools[i].ferula.Initialize(pools[i].count, pools[i].prefab, objectsParent.transform); } } } public static GameObject GetObject (string name, Vector3 position, Quaternion rotation) { GameObject result = null; if (pools != null) { for (int i = 0; i < pools.Length; i++) { if (string.Compare (pools [i].name, name) == 0) { result = pools[i].ferula.GetObject ().gameObject; result.transform.position = position; result.transform.rotation = rotation; result.SetActive (true); return result; } } } return result; } }
using UnityEngine; using System.Collections; [AddComponentMenu("Pool/PoolSetup")] public class PoolSetup : MonoBehaviour {//обертка для управления статическим классом PoolManager #region Unity scene settings [SerializeField] private PoolManager.PoolPart[] pools; #endregion #region Methods void OnValidate() { for (int i = 0; i < pools.Length; i++) { pools[i].name = pools[i].prefab.name; } } void Awake() { Initialize (); } void Initialize () { PoolManager.Initialize(pools); } #endregion }
ссылка на оригинал статьи http://habrahabr.ru/post/275091/
Добавить комментарий