Lazy person's asset pooling in Unity
WARNING! Artists and designers avert your eyes, run while you can, programming content to follow!
Due to some performance issues, we needed to implement asset pooling. I wanted a solution that is really straightforward to use, and can be reused in any of our projects. It has turned out to be fairly useful so far and easier to set up/use than other solutions I've seen, so I thought I would share it in case anyone here finds it useful.
A few things to be aware of:
* You need to be sure that your objects are indeed reuseable and provide your own Restart function for each.
* Destroying an object using this AssetManager will not let subsequent equality comparisons to null return true so be aware of that if you use that to determine whether an object has been destroyed.
* Not all objects give a significant performance boost when pooled! The more complex the object, the greater the benefit, and if you are instancing only one or two per frame the benefit is neglible. I made a thread on the unity forums regarding some odd findings regarding this at http://forum.unity3d.com/threads/159158-SetActiveRecursively-vs-Instantiate-Destroy-performance
* This asset manager automatically grows cached copies as it is used meaning that the first time you instance a specific prefab you still suffer the normal performance penalty. Prewarming the cache is trivial though and left as an excercise to the reader :P
Let me know if there are any blatant bugs/inefficiencies. There are probably better ways to implement pooling, but none that I can think of that are plug-and-play into any project such as this.
Usage:
Import the following two scripts into your project. Use AssetManager.Instantiate to instantiate a prefab that you want to have pooled, and AssetManager.Destroy to eventually destroy that object. That's all you need to know. A lightweight AssetInfo component is added to each object to track which prefab it was instantiated from. You can use AssetManager.GetDebugInfo for information on how many objects are in the pool.
Code:
AssetManager.cs
AssetInfo.cs
Due to some performance issues, we needed to implement asset pooling. I wanted a solution that is really straightforward to use, and can be reused in any of our projects. It has turned out to be fairly useful so far and easier to set up/use than other solutions I've seen, so I thought I would share it in case anyone here finds it useful.
A few things to be aware of:
* You need to be sure that your objects are indeed reuseable and provide your own Restart function for each.
* Destroying an object using this AssetManager will not let subsequent equality comparisons to null return true so be aware of that if you use that to determine whether an object has been destroyed.
* Not all objects give a significant performance boost when pooled! The more complex the object, the greater the benefit, and if you are instancing only one or two per frame the benefit is neglible. I made a thread on the unity forums regarding some odd findings regarding this at http://forum.unity3d.com/threads/159158-SetActiveRecursively-vs-Instantiate-Destroy-performance
* This asset manager automatically grows cached copies as it is used meaning that the first time you instance a specific prefab you still suffer the normal performance penalty. Prewarming the cache is trivial though and left as an excercise to the reader :P
Let me know if there are any blatant bugs/inefficiencies. There are probably better ways to implement pooling, but none that I can think of that are plug-and-play into any project such as this.
Usage:
Import the following two scripts into your project. Use AssetManager.Instantiate to instantiate a prefab that you want to have pooled, and AssetManager.Destroy to eventually destroy that object. That's all you need to know. A lightweight AssetInfo component is added to each object to track which prefab it was instantiated from. You can use AssetManager.GetDebugInfo for information on how many objects are in the pool.
Code:
AssetManager.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; public static class AssetManager { private static Dictionary<GameObject, List <GameObject>> assets; private static Dictionary<GameObject,List <GameObject>> Assets { get { if (assets == null) { assets = new Dictionary <GameObject, List<GameObject>>(); } return assets; } } public static GameObject Instantiate (GameObject prefab) { return Instantiate (prefab, Vector3.zero, Quaternion.identity); } public static GameObject Instantiate (GameObject prefab, Vector3 position, Quaternion rotation) { if (!Assets.ContainsKey(prefab)) { Assets.Add(prefab, new List<GameObject>()); } var list = Assets[prefab]; GameObject asset; if (list.Count > 0) { asset = list[list.Count-1]; list.RemoveAt(list.Count-1); asset.transform.position = position; asset.transform.rotation = rotation; asset.SetActiveRecursively(true); objReused++; } else { asset = GameObject.Instantiate(prefab, position,rotation) as GameObject; asset.AddComponent<AssetInfo>().Prefab = prefab; GameObject.DontDestroyOnLoad(asset); objCreated++; } return asset; } public static void Destroy (GameObject gameObject) { if (gameObject.GetComponent<AssetInfo>() == null) { Debug.LogError("You tried to use AssetManager.Destroy on a gameobject with no AssetInfo \n either you manually removed the assetinfo, or you should check that this object was indeed instantiated by the assetmanager."); GameObject.Destroy(gameObject); return; } if (Application.isEditor) { if (Assets[gameObject.GetComponent<AssetInfo>().Prefab].Contains(gameObject)) { Debug.LogError("Trying to add an object already in pool into pool! \n this means you called AssetManager.Destroy on an already-destroyed object."); return; } } gameObject.transform.parent = null; gameObject.transform.position = new Vector3 (-100f,-100f,-100f); gameObject.SetActiveRecursively(false); Assets[gameObject.GetComponent<AssetInfo>().Prefab].Add(gameObject); } static int objCreated, objReused; public static string GetDebugInfo() { int pooledObjects = 0; string typeInfo = ""; foreach (var ptype in Assets.Keys) { pooledObjects += Assets[ptype].Count; typeInfo += "\n" + ptype.name + ": " + Assets[ptype].Count; } return "Objects in pool: " + pooledObjects + "Types in pool:" + Assets.Count + " \nCreated: " +objCreated + " \nReused: " + objReused + typeInfo; } }
AssetInfo.cs
using UnityEngine; using System.Collections; public class AssetInfo : MonoBehaviour { public GameObject Prefab {get; set;} void OnDestory () { Debug.LogWarning("Attempting to destory an object that is being tracked by the Asset Manager! \n Nothing bad will happen but the performance benefit is lost."); } void OnLevelWasLoaded() { AssetManager.Destroy(this.gameObject); } }
Thanked by 1Karuji