Не так давно мы рассматривали паттерн пулы объектов, создав их минималистичную и довольно прямолинейную реализацию. Она решала поставленную перед ней задачу, но всё-таки была не очень удобна в использовании, так как вводила дополнительные сущности в виде имён, которые приходилось передавать между объектами. В этой статье мы приведём обновлённую версию пулов, которая лишена этих недостатков.
Singleton
Для начала хотелось бы сделать из пула объектов синглетон, это поможет избежать лишней передачи ссылки в использующие пулы объекты.
Эта реализация ничем не отличается от приведённой в соответствующей статье, кроме названия, с целью защиты от конфликтов.
usingUnityEngine;publicclassKhtSingleton<T>:MonoBehaviourwhereT:KhtSingleton<T>{privatestaticT_instance;publicstaticTInstance{get=>_instance;}publicstaticboolIsInstantiated{get=>_instance!=null;}protectedvirtualvoidAwake(){if(_instance!=null){Debug.LogError("["+typeof(T)+"] '"+transform.name+"' trying to instantiate a second instance of singleton class previously created in '"+_instance.transform.name+"'");}else{_instance=(T)this;}}protectedvoidOnDestroy(){if(_instance==this){_instance=null;}}}
Object pool
Данная реализация основана на использовании метода GetInstanceID() который позволяет получить уникальный идентификатор объекта.
Пулы привязываются к идентификатору префаба. В случае отсутствия пула для префаба создаётся новый, поэтому можно не задавать список пулов заранее, они будут созданы по мере необходимости. Новые объекты привязываются к пулу также по идентификатору.
В случае указания префаба для пула заранее, можно настроить его предварительное наполнение.
При отсутствии синглетона, предоставляющего работу с пулами, новые объекты будут создаваться и удаляться через Instantiate() и Destroy()
usingSystem;usingSystem.Collections.Generic;usingUnityEngine;publicclassKhtPool:KhtSingleton<KhtPool>{// Хак из-за отсутствия поддержки отображения Dictionary в инспекторе
// Словарь с необходимыми объектами будет создаваться из листа с описанием префабов
[Serializable]publicclassPrefabData{publicGameObjectprefab;publicintinitPoolSize=0;} [SerializeField]privateList<PrefabData>prefabDatas=null;// Привязка пула к идентификатору префаба
privatereadonlyDictionary<int,Queue<GameObject>>_pools=newDictionary<int,Queue<GameObject>>();// Привязка объекта к пулу по его идентификатору
privatereadonlyDictionary<int,int>_objectToPoolDict=newDictionary<int,int>();privatenewvoidAwake(){// Настройка синглетона
base.Awake();// При необходимости предварительно наполняем пулы объектов
foreach(varprefabDatainprefabDatas){_pools.Add(prefabData.prefab.GetInstanceID(),newQueue<GameObject>());for(inti=0;i<prefabData.initPoolSize;i++){GameObjectretObject=Instantiate(prefabData.prefab,Instance.transform,true);Instance._objectToPoolDict.Add(retObject.GetInstanceID(),prefabData.prefab.GetInstanceID());Instance._pools[prefabData.prefab.GetInstanceID()].Enqueue(retObject);retObject.SetActive(false);}}prefabDatas=null;}// Получение объекта из пула
publicstaticGameObjectGetObject(GameObjectprefab){// В случае отсутствия синглетона просто создаём новый объект
if(!Instance){returnInstantiate(prefab);}// Уникальный идентификатор префаба, по которому осуществляется привязка к пулу
intprefabId=prefab.GetInstanceID();// Если пула для префаба не существует, то создаём новый
if(!Instance._pools.ContainsKey(prefabId)){Instance._pools.Add(prefabId,newQueue<GameObject>());}// При наличии объекта в пуле возвращаем его
if(Instance._pools[prefabId].Count>0){returnInstance._pools[prefabId].Dequeue();}// В случае нехватки объектов создаём новый
GameObjectretObject=Instantiate(prefab);// Добавляем привязку объекта к пулу по его идентификатору
Instance._objectToPoolDict.Add(retObject.GetInstanceID(),prefabId);returnretObject;}// Возврат объекта в пул
publicstaticvoidReturnObject(GameObjectpoolObject){// В случае отсутствия синглетона просто уничтожаем объект
if(!Instance){Destroy(poolObject);return;}// Идентификатор объекта для определения пула
intobjectId=poolObject.GetInstanceID();// В случае отсутствия привязки объекта к пулу просто его уничтожаем
if(!Instance._objectToPoolDict.TryGetValue(objectId,outintpoolId)){Destroy(poolObject);return;}// Возвращаем объект в пул
Instance._pools[poolId].Enqueue(poolObject);poolObject.transform.SetParent(Instance.transform);poolObject.SetActive(false);}}
usingUnityEngine;publicclassFire:MonoBehaviour{// Информация, не относящаяся к пулам
[SerializeField]privateTransformleftCannon=null; [SerializeField]privateTransformrightCannon=null; [SerializeField]privateUnevirseHandleruniverse=null; [SerializeField]privateTransformspace=null;// Префаб для получения объекта из пула
[SerializeField]privateGameObjectprojectilePrefab=null;privatebool_rightCannon=false;voidUpdate(){if(Input.GetMouseButtonDown(0)){// Получени объекта из пула
GameObjectpr=KhtPool.GetObject(projectilePrefab);// Объекты из пула нуждаются в правильной очистке и инициализации
pr.transform.SetParent(space);pr.transform.SetPositionAndRotation(_rightCannon?rightCannon.position:leftCannon.position,_rightCannon?rightCannon.rotation:leftCannon.rotation);Projectileprpr=pr.GetComponent<Projectile>();prpr.MoveVector=universe.LookVector.normalized;pr.SetActive(true);_rightCannon=!_rightCannon;}}}
usingUnityEngine;publicclassProjectile:MonoBehaviour{ [SerializeField]privatefloatspeed=0;privateVector2_moveVector=Vector2.zero;publicVector2MoveVector{set=>_moveVector=value;}voidUpdate(){transform.Translate(_moveVector.x*Time.deltaTime*speed,_moveVector.y*Time.deltaTime*speed,0,Space.World);}voidOnBecameInvisible(){// После выхода объекта за границы экрана возвращаем его в пул
KhtPool.ReturnObject(gameObject);}}
Результат
Новые объекты создаются при необходимости:
Заключение
Обновлённая версия сильно упрощает нашу предыдущую реализацию, хотя и расширяет возможности для добавления дополнительного функционала, такого как запрет на создание новых пулов, или запрет их расширения. Но эти фичи используются сильно реже, чем предварительное наполнение, так что оставим их на будущее. Пока! =)