├── SimplePoolUtils.cs ├── SimplePool.cs └── README.md /SimplePoolUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | public static class SimplePoolHelper 4 | { 5 | // Pool container for given type 6 | private static class PoolsOfType where T : class 7 | { 8 | // Pool with poolName = null 9 | private static SimplePool defaultPool = null; 10 | 11 | // Other pools 12 | private static Dictionary> namedPools = null; 13 | 14 | public static SimplePool GetPool( string poolName = null ) 15 | { 16 | if( poolName == null ) 17 | { 18 | if( defaultPool == null ) 19 | defaultPool = new SimplePool(); 20 | 21 | return defaultPool; 22 | } 23 | else 24 | { 25 | SimplePool result; 26 | 27 | if( namedPools == null ) 28 | { 29 | namedPools = new Dictionary>(); 30 | 31 | result = new SimplePool(); 32 | namedPools.Add( poolName, result ); 33 | } 34 | else if( !namedPools.TryGetValue( poolName, out result ) ) 35 | { 36 | result = new SimplePool(); 37 | namedPools.Add( poolName, result ); 38 | } 39 | 40 | return result; 41 | } 42 | } 43 | } 44 | 45 | // NOTE: if you don't need two or more pools of same type, 46 | // leave poolName as null while calling any of these functions 47 | // for better performance 48 | 49 | public static SimplePool GetPool( string poolName = null ) where T : class 50 | { 51 | return PoolsOfType.GetPool( poolName ); 52 | } 53 | 54 | public static void Push( T obj, string poolName = null ) where T : class 55 | { 56 | PoolsOfType.GetPool( poolName ).Push( obj ); 57 | } 58 | 59 | public static T Pop( string poolName = null ) where T : class 60 | { 61 | return PoolsOfType.GetPool( poolName ).Pop(); 62 | } 63 | 64 | // Extension method as a shorthand for Push function 65 | public static void Pool( this T obj, string poolName = null ) where T : class 66 | { 67 | PoolsOfType.GetPool( poolName ).Push( obj ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SimplePool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Object = UnityEngine.Object; 4 | 5 | // Generic pool class 6 | // Author: Suleyman Yasir Kula 7 | // Feel free to use/upgrade 8 | public class SimplePool where T : class 9 | { 10 | // Objects stored in the pool 11 | private Stack pool = null; 12 | 13 | // Blueprint to use for instantiation 14 | public T Blueprint { get; set; } 15 | 16 | // A function that can be used to override default NewObject( T ) function 17 | public Func CreateFunction; 18 | 19 | // Actions that can be used to implement extra logic on pushed/popped objects 20 | public Action OnPush, OnPop; 21 | 22 | public SimplePool( Func CreateFunction = null, Action OnPush = null, Action OnPop = null ) 23 | { 24 | pool = new Stack(); 25 | 26 | this.CreateFunction = CreateFunction; 27 | this.OnPush = OnPush; 28 | this.OnPop = OnPop; 29 | } 30 | 31 | public SimplePool( T blueprint, Func CreateFunction = null, Action OnPush = null, Action OnPop = null ) 32 | : this( CreateFunction, OnPush, OnPop ) 33 | { 34 | // Set the blueprint at creation 35 | Blueprint = blueprint; 36 | } 37 | 38 | // Populate the pool with the default blueprint 39 | public bool Populate( int count ) 40 | { 41 | return Populate( Blueprint, count ); 42 | } 43 | 44 | // Populate the pool with a specific blueprint 45 | public bool Populate( T blueprint, int count ) 46 | { 47 | if( count <= 0 ) 48 | return true; 49 | 50 | // Create a single object first to see if everything works fine 51 | // If not, return false 52 | T obj = NewObject( blueprint ); 53 | if( obj == null ) 54 | return false; 55 | 56 | Push( obj ); 57 | 58 | // Everything works fine, populate the pool with the remaining items 59 | for( int i = 1; i < count; i++ ) 60 | { 61 | Push( NewObject( blueprint ) ); 62 | } 63 | 64 | return true; 65 | } 66 | 67 | // Fetch an item from the pool 68 | public T Pop() 69 | { 70 | T objToPop; 71 | 72 | if( pool.Count == 0 ) 73 | { 74 | // Pool is empty, instantiate the blueprint 75 | 76 | objToPop = NewObject( Blueprint ); 77 | } 78 | else 79 | { 80 | // Pool is not empty, fetch the first item in the pool 81 | 82 | objToPop = pool.Pop(); 83 | while( objToPop == null ) 84 | { 85 | // Some objects in the pool might have been destroyed (maybe during a scene transition), 86 | // consider that case 87 | if( pool.Count > 0 ) 88 | objToPop = pool.Pop(); 89 | else 90 | { 91 | objToPop = NewObject( Blueprint ); 92 | break; 93 | } 94 | } 95 | } 96 | 97 | if( OnPop != null ) 98 | OnPop( objToPop ); 99 | 100 | return objToPop; 101 | } 102 | 103 | // Fetch multiple items at once from the pool 104 | public T[] Pop( int count ) 105 | { 106 | if( count <= 0 ) 107 | return new T[0]; 108 | 109 | T[] result = new T[count]; 110 | for( int i = 0; i < count; i++ ) 111 | result[i] = Pop(); 112 | 113 | return result; 114 | } 115 | 116 | // Pool an item 117 | public void Push( T obj ) 118 | { 119 | if( obj == null ) return; 120 | 121 | if( OnPush != null ) 122 | OnPush( obj ); 123 | 124 | pool.Push( obj ); 125 | } 126 | 127 | // Pool multiple items at once 128 | public void Push( IEnumerable objects ) 129 | { 130 | if( objects == null ) return; 131 | 132 | foreach( T obj in objects ) 133 | Push( obj ); 134 | } 135 | 136 | // Clear the pool 137 | public void Clear( bool destroyObjects = true ) 138 | { 139 | if( destroyObjects ) 140 | { 141 | // Destroy all the Objects in the pool 142 | foreach( T item in pool ) 143 | { 144 | Object.Destroy( item as Object ); 145 | } 146 | } 147 | 148 | pool.Clear(); 149 | } 150 | 151 | // Create an instance of the blueprint and return it 152 | private T NewObject( T blueprint ) 153 | { 154 | if( CreateFunction != null ) 155 | return CreateFunction( blueprint ); 156 | 157 | if( blueprint == null || !( blueprint is Object ) ) 158 | return null; 159 | 160 | return Object.Instantiate( blueprint as Object ) as T; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityGenericPool 2 | A pooling solution for Unity3D that can store "almost" anything; ranging from Unity objects (e.g. Component, GameObject, Texture) to plain C# objects. It can't store structs but since they are pass-by-value, that would be pointless. 3 | 4 | This pool comes with a helper class (SimplePoolHelper) that provides global access to pools that can optionally be named. It is no longer necessary to keep references to pools in your scripts (however, if you wish, you can ignore SimplePoolHelper and manage your pools manually). Note that SimplePoolHelper pools are stored statically and are persistent between scenes. 5 | 6 | # Method Signatures 7 | ```C# 8 | SimplePool: 9 | 10 | // blueprint: object that gets instantiated when the pool is empty (only if T is of type UnityEngine.Object); also passed as parameter to the CreateFunction 11 | // CreateFunction: called when the pool needs to be populated. Takes blueprint as parameter and should return a new object (if left null and if T is of type UnityEngine.Object, blueprint will be instantiated) 12 | // OnPush: called when an object is pushed to the pool; can be used to e.g. deactivate the object 13 | // OnPop: called when an object is popped from the pool; can be used to e.g. activate the object 14 | SimplePool( Func CreateFunction = null, Action OnPush = null, Action OnPop = null ); 15 | SimplePool( T blueprint, Func CreateFunction = null, Action OnPush = null, Action OnPop = null ); 16 | 17 | bool Populate( int count ); 18 | bool Populate( T blueprint, int count ); 19 | T Pop(); 20 | T[] Pop( int count ); 21 | void Push( T obj ); 22 | void Push( IEnumerable objects ); 23 | void Clear( bool destroyObjects = true ); 24 | 25 | SimplePoolHelper: 26 | 27 | static SimplePool GetPool( string poolName = null ); 28 | static void Push( T obj, string poolName = null ); 29 | static T Pop( string poolName = null ); 30 | static void Pool( this T obj, string poolName = null ); 31 | ``` 32 | 33 | # Example Code 34 | ```C# 35 | using UnityEngine; 36 | 37 | public class PlayerWeapons : MonoBehaviour 38 | { 39 | public Transform bulletPrefab; 40 | public Transform grenadePrefab; 41 | 42 | // Called when the scene starts 43 | void Awake() 44 | { 45 | // Get a Transform pool called Bullets 46 | SimplePool bulletsPool = SimplePoolHelper.GetPool( "Bullets" ); 47 | 48 | // Get another Transform pool called Grenades 49 | SimplePool grenadesPool = SimplePoolHelper.GetPool( "Grenades" ); 50 | 51 | // When a bullet is pooled, deactivate it 52 | bulletsPool.OnPush = ( item ) => item.gameObject.SetActive( false ); 53 | 54 | // When a bullet is fetched from the pool, activate it and set its position 55 | bulletsPool.OnPop = ( item ) => 56 | { 57 | item.gameObject.SetActive( true ); 58 | item.position = transform.position; 59 | item.rotation = transform.rotation; 60 | }; 61 | 62 | // When pool tries to create a new bullet (when empty), create an instance of bulletPrefab and keep it alive between scenes 63 | bulletsPool.CreateFunction = ( template ) => 64 | { 65 | Transform newBullet = Instantiate( bulletPrefab ); 66 | DontDestroyOnLoad( newBullet ); 67 | return newBullet; 68 | }; 69 | 70 | // When a grenade is pooled, deactivate it 71 | grenadesPool.OnPush = ( item ) => item.gameObject.SetActive( false ); 72 | // When a grenade is fetched from the pool, activate it (you can also set its position here, it is entirely up to you) 73 | grenadesPool.OnPop = ( item ) => item.gameObject.SetActive( true ); 74 | // When pool tries to create a new grenade, simply create an instance of grenadePrefab 75 | grenadesPool.CreateFunction = ( template ) => Instantiate( grenadePrefab ); 76 | 77 | // Populate the pool with 10 bullet instances (optional) 78 | bulletsPool.Populate( 10 ); 79 | 80 | // Populate the pool with 4 grenade instances (optional) 81 | grenadesPool.Populate( 4 ); 82 | 83 | // Yet another pool that will store some WaitForSeconds instances for reuse in Grenade class 84 | // This pool is different from the previous pools in two ways: 85 | // 1) it stores a plain object: WaitForSeconds, which is not a Component nor a GameObject 86 | // 2) it has no name since there is only one pool of type WaitForSeconds; note that 87 | // it is faster and more efficient to access an unnamed pool in SimplePoolHelper 88 | // CreateFunction: simply create a new WaitForSeconds instance that waits for 6 seconds 89 | // There is no need to do any special operations on OnPush or OnPop, so they are not altered 90 | SimplePoolHelper.GetPool().CreateFunction = ( template ) => new WaitForSeconds( 6f ); 91 | } 92 | 93 | // Called when the object is destroyed 94 | void OnDestroy() 95 | { 96 | // This transform is about to be destroyed, bullets fetched from pool can no longer use it 97 | // So fetched bullets are simply activated, without changing their position 98 | SimplePoolHelper.GetPool( "Bullets" ).OnPop = ( item ) => item.gameObject.SetActive( true ); 99 | 100 | // Grenades in the pool are not persistent between scenes (unlike bullets), and will become null references 101 | // So clear all grenade instances in the Grenades pool 102 | SimplePoolHelper.GetPool( "Grenades" ).Clear(); 103 | } 104 | 105 | void Update() 106 | { 107 | // Left mouse button clicked: fire a bullet 108 | if( Input.GetMouseButtonDown( 0 ) ) 109 | { 110 | // Add forward force to the bullet 111 | SimplePoolHelper.Pop( "Bullets" ).GetComponent().velocity = transform.forward * 100f; 112 | } 113 | 114 | // Right mouse button clicked: throw a grenade 115 | if( Input.GetMouseButtonDown( 1 ) ) 116 | { 117 | Transform grenade = SimplePoolHelper.Pop( "Grenades" ); 118 | 119 | // Set the position of the grenade (unlike bullets, it is not automatically set in Pop) 120 | grenade.position = transform.position; 121 | 122 | // Add forward force to the grenade (also some upwards force as well) 123 | grenade.GetComponent().velocity = transform.forward * 30f + transform.up * 15f; 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | ```C# 130 | using UnityEngine; 131 | 132 | public class Bullet : MonoBehaviour 133 | { 134 | // Pool the bullet if it doesn't hit anything in 5 seconds 135 | void OnEnable() 136 | { 137 | Invoke( "PoolBullet", 5f ); 138 | } 139 | 140 | // Bullet is deactivated (probably hit something and got pooled), cancel the PoolBullet invoke just in case 141 | void OnDisable() 142 | { 143 | CancelInvoke( "PoolBullet" ); 144 | } 145 | 146 | void OnCollisionEnter( Collision other ) 147 | { 148 | // Apply damage to the collided entity 149 | Health entityHealth = other.transform.GetComponent(); 150 | if( entityHealth != null ) 151 | entityHealth.ApplyDamage( 10 ); 152 | 153 | // Pool the bullet instead of destroying it 154 | // As bullets are kept in a Transform pool, the Pool extension function is used on the transform of the pool 155 | transform.Pool( "Bullets" ); 156 | 157 | // Alternative pool method (does exactly the same thing as Pool function) 158 | // SimplePoolHelper.Push( transform, "Bullets" ); 159 | } 160 | 161 | private void PoolBullet() 162 | { 163 | transform.Pool( "Bullets" ); 164 | } 165 | } 166 | ``` 167 | 168 | ```C# 169 | using UnityEngine; 170 | using System.Collections; 171 | 172 | public class Grenade : MonoBehaviour 173 | { 174 | // Explode after 6 seconds 175 | // Notice how we use OnEnable instead of Start as Start is only called once during the lifetime of the object 176 | void OnEnable() 177 | { 178 | StartCoroutine( Explode() ); 179 | } 180 | 181 | IEnumerator Explode() 182 | { 183 | // Fetch an instance of WaitForSeconds from the pool 184 | WaitForSeconds waitForSeconds = SimplePoolHelper.Pop(); 185 | 186 | // Wait for 6 seconds (set in PlayerWeapons class) 187 | yield return waitForSeconds; 188 | 189 | // Pool the WaitForSeconds instance for reuse 190 | waitForSeconds.Pool(); 191 | 192 | // Play some fancy particles 193 | GetComponent().Emit( 50 ); 194 | 195 | // Pool the grenade for reuse 196 | transform.Pool( "Grenades" ); 197 | } 198 | } 199 | ``` 200 | --------------------------------------------------------------------------------