I tried to come up with my own generic pool and I wanted to hear your advices about it and what other kind of solution you came up. I first tried to use generic type and interface like Code (CSharp): public class Pool<T> : MonoBehaviour where T : MonoBehaviour, IPoolable This would have allowed me to simply add the interface to any script to be able to use it in a pool. But in the end I found out that I can't have any generic Type to a child class of MonoBehaviour So instead I made a Code (CSharp): public class BasicPoolObject : MonoBehaviour Any object I wanted to be pooled will need to have this component or be its child. In the end however I am quite happy at how well it work. It pool a reference to any type of prefab you can get an object by either sending the prefab or the name of the prefab if it is already prefab pooled. The only issue I can see is if 2 different prefabs with the same name is sent to the pool, it won't give any error but will always send the first one. I am not an expert on benchmarking, I would like to know how efficient you think it is. Code (CSharp): using System.Collections; using System.Collections.Generic; using UnityEngine; public class Pool : MonoBehaviour { private static Dictionary<string, BasicPoolObject> pooledPrefabs; private static Dictionary<string, List<BasicPoolObject>> pool; //singleton pattern implementation private static Pool _pool; public static Pool Instance { get { if (!_pool) { _pool = FindObjectOfType(typeof(Pool)) as Pool; if (!_pool) { Debug.Log("Error, no Pool on scene"); } } else { _pool.Init(); } return _pool; } } private void Awake() { Instance.Init(); } private void Init() { pooledPrefabs = new Dictionary<string, BasicPoolObject>(); pool = new Dictionary<string, List<BasicPoolObject>>(); } public static BasicPoolObject GetObject(string prefabName) { BasicPoolObject prefab; pooledPrefabs.TryGetValue(prefabName, out prefab); return GetObject(prefab); } public static BasicPoolObject GetObject(BasicPoolObject prefab) { if (prefab == null) return null; BasicPoolObject value; //try to find if this type of prefab is already pooled if(!pooledPrefabs.TryGetValue(prefab.name,out value)) {//add the prefab to the pooled prefabs and init its pool list pooledPrefabs.Add(prefab.name, prefab); pool.Add(prefab.name, new List<BasicPoolObject>()); value = prefab; } BasicPoolObject inactiveObject = FindInactiveObjectInPool(pool[value.name]); if(inactiveObject == null) {//create object int index = pool[value.name].Count; pool[value.name].Add(CreateObject(value)); inactiveObject = pool[value.name][index]; inactiveObject.Init(pool[value.name].Count - 1, value.name); } inactiveObject.gameObject.SetActive(true); inactiveObject.Reset(); return inactiveObject; } /// <summary> /// return true if a new prefab is added /// </summary> /// <param name="prefab"></param> /// <returns></returns> public static bool AddPrefab(BasicPoolObject prefab) { if(pooledPrefabs.ContainsKey(prefab.poolName)) { return false; } else { pooledPrefabs.Add(prefab.name, prefab); pool.Add(prefab.name, new List<BasicPoolObject>()); return true; } } public static bool DeactivateObject(BasicPoolObject poolObject) { List<BasicPoolObject> objList; if (pool.TryGetValue(poolObject.poolName, out objList)) { if(poolObject.index < objList.Count) { objList[poolObject.index].gameObject.SetActive(false); return true; } } return false; } private static BasicPoolObject FindInactiveObjectInPool(List<BasicPoolObject> poolList) { return poolList.Find(x => !x.gameObject.activeSelf); } private static BasicPoolObject CreateObject(BasicPoolObject prefab) { BasicPoolObject createdObject = Instantiate(prefab, prefab.transform.position, prefab.transform.rotation).GetComponent<BasicPoolObject>(); return createdObject; } }
Why don't you just pool the GameObject or Transform of the instance? What benefit does a certain component give you? Why do you use string as key in your Dictionary? I would prefer an enum with the "type" of object to spawn. If the pool does not find the object just create it and add the component. It seems your pool contains all pooled objects. I would consider a pool to only contain objects of a certain type. A poolmanager would hold a list of pools and would be indexed by an Enum. There are some free pooling solutions on the asset store (and probably in blogs and tutorials too). Have a look at them for inspiration. But in the end when it works for you it is fine.
I do like using static pools on generic classes instead of having a central pool manager. Just having a concrete implementation of the generic class implicitly creates a pool and you don't have to look up the right pool from the pool manager. It's also more type-safe than using string or enums as identifiers. Note sure what your issue was here? Here's a quick example of how it could work: Code (CSharp): public abstract class Poolable : MonoBehaviour { public abstract void Initialize(); public abstract void Reset(); } public abstract class Poolable<T> : Poolable where T : Poolable { static Queue<T> pool = new Queue<T>(); public static T Get() { T item; if (pool.Count > 0) { item = pool.Dequeue(); } else { var go = new GameObject(nameof(T)); item = go.AddComponent<T>(); } item.Initialize(); return item; } public static void Return(T item) { item.Reset(); pool.Enqueue(item); } } public class MyBehaviour : Poolable<MyBehaviour> { public override void Initialize() { // } public override void Reset() { // } } var instance = MyBehaviour.Get(); // do something with it... MyBehaviour.Return(instance);
A little late but I reworked my pool system, I know there are premade solution but I am working on a test project as a hobby to better my skills. the code is cleaner and more efficient, and now I can use the IPoolable interface so it will be way simpler do add stuff to the pool, Im still new at using interfaces and generics. The other biggest change is that I do not have to do a for loop to find and inactive object I will probably change the string PoolName to an Enum later Code (CSharp): public interface IPoolable<T> where T : MonoBehaviour { T Mono { get;} int Index { get;} string PoolName { get; } void Init(int _index, string _poolName); void Reset(); } A pool of 1 object type Code (CSharp): public class Pool<T> where T : IPoolable<MonoBehaviour> { private Dictionary<int, T> activePool; private Queue<T> inactivePool; private string poolName; private T basePrefab; public T BasePrefab { get => basePrefab; } public Pool(IPoolable<MonoBehaviour> prefab) { activePool = new Dictionary<int, T>(); inactivePool = new Queue<T>(); poolName = prefab.PoolName; basePrefab = (T)prefab; } public T GetItem() { if (inactivePool.Count == 0) return default(T); T item = inactivePool.Dequeue(); activePool.Add(item.Index, item); item.Mono.gameObject.SetActive(true); item.Reset(); return item; } /// <summary> /// Only send instantiated active objects, return as active /// </summary> public void AddItem(T item) { item.Init(activePool.Count + inactivePool.Count, poolName); activePool.Add(item.Index, item); item.Reset(); } /// <summary> /// Only send instantiated inactive objects /// </summary> /// <param name="item"></param> public void AddInactiveItem(T item) { item.Init(activePool.Count + inactivePool.Count, poolName); inactivePool.Enqueue(item); } public bool DeactivateItem(T item) { return DeactivateItem(item.Index); } public bool DeactivateItem(int itemIndex) { T item; if (!activePool.TryGetValue(itemIndex, out item)) { return false; } activePool.Remove(itemIndex); item.Mono.gameObject.SetActive(false); inactivePool.Enqueue(item); return true; } } The cleaner PoolManager Code (CSharp): public class PoolManager : MonoBehaviour { private static Dictionary<string, Pool<IPoolable<MonoBehaviour>>> pools; //singleton pattern implementation private static PoolManager _pool; public static PoolManager Instance { get { if (!_pool) { _pool = FindObjectOfType(typeof(PoolManager)) as PoolManager; if (!_pool) { Debug.Log("Error, no Pool on scene"); } } else { _pool.Init(); } return _pool; } } private void Awake() { Instance.Init(); } private void Init() { //pooledPrefabs = new Dictionary<string, BasicPoolObject>(); pools = new Dictionary<string, Pool<IPoolable<MonoBehaviour>>>(); } public static IPoolable<MonoBehaviour> GetObject(string prefabName) { Pool<IPoolable<MonoBehaviour>> prefabPool; if (!pools.TryGetValue(prefabName, out prefabPool)) return null; return GetObject(prefabPool.BasePrefab); } public static IPoolable<MonoBehaviour> GetObject(IPoolable<MonoBehaviour> prefab) { if (prefab == null) return null; Pool<IPoolable<MonoBehaviour>> pool; //try to find if this type of prefab is already pooled if (!pools.TryGetValue(prefab.PoolName, out pool)) {//add the prefab to the pooled prefabs and init its pool list pools.Add(prefab.PoolName, new Pool<IPoolable<MonoBehaviour>>(prefab)); pool = pools[prefab.PoolName]; } IPoolable<MonoBehaviour> item = pool.GetItem(); //create object if(item == null) { item = CreateObject(prefab); pool.AddItem(item); } return item; } /// <summary> /// create a new pool with a prefab reference /// </summary> /// <param name="prefab"></param> /// <returns></returns> public static bool AddPool(IPoolable<MonoBehaviour> prefab) { if (pools.ContainsKey(prefab.PoolName)) { return false; } else { pools.Add(prefab.PoolName, new Pool<IPoolable<MonoBehaviour>>(prefab)); return true; } } public static bool DeactivateObject(IPoolable<MonoBehaviour> poolObject) { Pool<IPoolable<MonoBehaviour>> pool; if (pools.TryGetValue(poolObject.PoolName, out pool)) { return pool.DeactivateItem(poolObject.Index); } return false; } private static BasicPoolObject FindInactiveObjectInPool(List<BasicPoolObject> poolList) { return poolList.Find(x => !x.gameObject.activeSelf); } private static IPoolable<MonoBehaviour> CreateObject(IPoolable<MonoBehaviour> prefab) { GameObject createdObject = Instantiate(prefab.Mono.gameObject, prefab.Mono.transform.position, prefab.Mono.transform.rotation); return (IPoolable<MonoBehaviour>) createdObject.GetComponent(typeof(IPoolable<MonoBehaviour>)); } } A IPoolable implementation exemple with a function to test the pool system Code (CSharp): public class BasicPoolObject : MonoBehaviour, IPoolable<MonoBehaviour> { public MonoBehaviour Mono { get => this; } public int Index { get => index; } public string PoolName { get => poolName; } private int index; [SerializeField] private string poolName; public float endDelay; public void Init(int _index, string _poolName) { index = _index; poolName = _poolName; } public void Reset() { StartCoroutine("Stop"); } private IEnumerator Stop() { yield return new WaitForSeconds(endDelay); PoolManager.DeactivateObject(this); } } a script to test the pool system Code (CSharp): public class PoolTest : MonoBehaviour { [SerializeField] BasicPoolObject[] prefabs; [SerializeField] BasicPoolObject poolTest; public float delay; void Start() { StartCoroutine("Spawn"); } private IEnumerator Spawn() { while (true) { PoolManager.GetObject(prefabs[0]); PoolManager.GetObject(prefabs[1]); yield return new WaitForSeconds(delay); } } }