In Unity, the Instantiate()
and Destroy()
functions are used to create copy of objects especially prefabs and destroy them. When it comes to pooling, the pool object is usually represented in the pool as a Type of GameObject. When you need to access a component from the pool you first retrieve the pool GameObject then use the GetComponent
function to retrieve the component from a GameObject.
Reading your question and comments carefully, you want to avoid the GetComponent
section and represent just the components not the GameObject so that you can also access the components directly.
If this is what you want then this is where Unity's Component
is required. See below for steps required to do this.
Note that when I say component/script, I am referring to your scripts that derive from MonoBehaviour
that can be attached to GameObjects or built-in Unity components such as Rigidbody
and BoxCollider
.
1. Store the components/scripts to a List of Component
.
List<Component> components;
2. Store the List of Components in a Dictionary with Type
as the key and List<Component>
as the value. This makes it easier and faster to group and find components by Type
.
Dictionary<Type, List<Component>> poolTypeDict;
3. The rest is really easy. Make the function that adds or retrieves the pool items from and to the Dictionary to be generic then use Convert.ChangeType
to cast between the generic type to Component
type or from generic to what ever type that is requested to be returned.
4. When you need to add item to the Dictionary, check if the Type
exist yet, if it does, retrieve the existing key, create and add new Component
to it with the Instantiate
function then save it to the Dictionary.
If the Type
doesn't exist yet, no need to retrieve any data from the Dictionary
. Simply create new one and add it to the Dictionary with its Type
.
Once you add item to the pool de-activate the GameObject with component.gameObject.SetActive(false)
5. When you need to retrieve an item from the pool, check if the Type
exist as key then retrieve the value which is List
of Component
. Loop over the components and return any component that has a de-activated GameObject. You can check that by checking if component.gameObject.activeInHierarchy
is false
.
Once you retrieve item from the pool activate the GameObject with component.gameObject.SetActive(true)
If no component is found, you can decide to either return null or instantiate new component.
6. To recycle the item back to the pool when you're done using it, you don't call the Destroy
function. Simply de-activate the GameObject with component.gameObject.SetActive(false)
*. This will make the component able to be found next time you search for available components in the Dictionary
and List
.
Below is an example of minimum generic pool system for scripts and components:
public class ComponentPool
{
//Determines if pool should expand when no pool is available or just return null
public bool autoExpand = true;
//Links the type of the componet with the component
Dictionary<Type, List<Component>> poolTypeDict = new Dictionary<Type, List<Component>>();
public ComponentPool() { }
//Adds Prefab component to the ComponentPool
public void AddPrefab<T>(T prefabReference, int count = 1)
{
_AddComponentType<T>(prefabReference, count);
}
private Component _AddComponentType<T>(T prefabReference, int count = 1)
{
Type compType = typeof(T);
if (count <= 0)
{
Debug.LogError("Count cannot be <= 0");
return null;
}
//Check if the component type already exist in the Dictionary
List<Component> comp;
if (poolTypeDict.TryGetValue(compType, out comp))
{
if (comp == null)
comp = new List<Component>();
//Create the type of component x times
for (int i = 0; i < count; i++)
{
//Instantiate new component and UPDATE the List of components
Component original = (Component)Convert.ChangeType(prefabReference, typeof(T));
Component instance = Instantiate(original);
//De-activate each one until when needed
instance.gameObject.SetActive(false);
comp.Add(instance);
}
}
else
{
//Create the type of component x times
comp = new List<Component>();
for (int i = 0; i < count; i++)
{
//Instantiate new component and UPDATE the List of components
Component original = (Component)Convert.ChangeType(prefabReference, typeof(T));
Component instance = Instantiate(original);
//De-activate each one until when needed
instance.gameObject.SetActive(false);
comp.Add(instance);
}
}
//UPDATE the Dictionary with the new List of components
poolTypeDict[compType] = comp;
/*Return last data added to the List
Needed in the GetAvailableObject function when there is no Component
avaiable to return. New one is then created and returned
*/
return comp[comp.Count - 1];
}
//Get available component in the ComponentPool
public T GetAvailableObject<T>(T prefabReference)
{
Type compType = typeof(T);
//Get all component with the requested type from the Dictionary
List<Component> comp;
if (poolTypeDict.TryGetValue(compType, out comp))
{
//Get de-activated GameObject in the loop
for (int i = 0; i < comp.Count; i++)
{
if (!comp[i].gameObject.activeInHierarchy)
{
//Activate the GameObject then return it
comp[i].gameObject.SetActive(true);
return (T)Convert.ChangeType(comp[i], typeof(T));
}
}
}
//No available object in the pool. Expand array if enabled or return null
if (autoExpand)
{
//Create new component, activate the GameObject and return it
Component instance = _AddComponentType<T>(prefabReference, 1);
instance.gameObject.SetActive(true);
return (T)Convert.ChangeType(instance, typeof(T));
}
return default(T);
}
}
public static class ExtensionMethod
{
public static void RecyclePool(this Component component)
{
//Reset position and then de-activate the GameObject of the component
GameObject obj = component.gameObject;
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
component.gameObject.SetActive(false);
}
}
USAGE:
It can take a any prefab component script. Prefabs are used for this since pooled objects are usually prefabs instantiated and waiting to be used.
Example prefab scripts (LandingFX
, BumperFX
) :
public class LandingFX : MonoBehaviour { ... }
and
public class BumperFX : MonoBehaviour { ... }
Two variables to hold the Prefabs references. You can either use public variables and assign them from the Editor or load them with the Resources API.
public LandingFX landingFxPrefab;
public BumperFX bumperFxPrefab;
Create new Component Pool and disable auto-resize
ComponentPool cmpPool = new ComponentPool();
cmpPool.autoExpand = false;
Create 2 pools for LandingFX and BumperFX components. It can take any component
//AddPrefab 2 objects type of LandingFX
cmpPool.AddPrefab(landingFxPrefab, 2);
//AddPrefab 2 objects type of BumperFX
cmpPool.AddPrefab(bumperFxPrefab, 2);
When you need a LandingFX
from the pool, you can retrieve them as below:
LandingFX lndngFX1 = cmpPool.GetAvailableObject(landingFxPrefab);
LandingFX lndngFX2 = cmpPool.GetAvailableObject(landingFxPrefab);
When you need a BumperFX
from the pool, you can retrieve them as below:
BumperFX bmpFX1 = cmpPool.GetAvailableObject(bumperFxPrefab);
BumperFX bmpFX2 = cmpPool.GetAvailableObject(bumperFxPrefab);
When you're done using the retrieved component, recycle them back to the pool instead of destroying them:
lndngFX1.RecyclePool();
lndngFX2.RecyclePool();
bmpFX1.RecyclePool();
bmpFX2.RecyclePool();