Repository navigation
Object Pooling
Object Pooling is a optimization system which will allows for the recycling of game objects without having to destroy and instantiate them. This is particularly useful for games which have a lot of quickly created and destroyed objects such as 'bullet hell' games that have significant amount of projectiles.
The design of the system is the followingL
-
Poolable- A component that must be attached to a game object you wish to keep in the Object Pool system. -
ObjectPool- A static class which manages allPoolablegame objects which are created using it's own but identicalInstantiatecommand. Do not use the standard game object'sInstantiatecommand forPoolablegame objects.
When the object is first instantiated it is Populated into the pool. This will only occur once when it is initially created. After it is populated it is then Retrieved from the pool into the active scene. At any point, according to your own application logic, the object will be Returned to the pool when it is done being used, where it will be inactive. The next time an poolable prefab is instantiated it will prioritize Retrieving an item from the pool rather than creating a new one.
If a poolable item is ever properly destroyed it will be Returned to the pool if currently active and then Disposed of.
graph TD;
Population-->Pool
Pool-->Retrieve
Return-->Pool
Pool-->Dispose
Given this lifecycle you can consider these states as practically:
-
Populate- The game object has been created for the very first time. Generic initialization such as GetComponent and likewise can be included here. -
Retrieve- The game object has been 'instantiated'. This can occur any number of times and logic here should initialize the object for this particular iteration of it such as resetting timers or such values. -
Return- The game object is done being used in the scene. This should clean up any state that shouldn't carry over into the next iteration of the object. -
Dispose- The game object is being destroyed, never to be used again. If you subscribed to any events inPopulatethis is a good place to unsubscribe.
For this example we are going to create a system that will pool projectiles in a shooter game.
Disclaimer: This example is primarily focused on specifically the object pool system not a practical shooter implementation.
First let's create a prefab for our games projectile. For this example it will just be a stationary cube in the game world.
- Create the cube by right clicking in the Hierarchy and choosing
Create -> 3D Object -> Cube - Name the Cube "Poolable Projectile"
- Drag the game object into the Project view to make it a prefab.
- Delete the instance of the prefab in the scene.
- With the "Poolable Projectile" open in the Prefab Editor add the
Poolablecomponent to the projectile.- This is required and enables our prefab to work within the
ObjectPoolsystem. - Remark: You may notice the
Pool Capacity Limit Behaviorfield. This allows you to optionally prevent the pool from getting to large through various methods. These behaviors will be covered in a later section.
- This is required and enables our prefab to work within the
- Add a
Rigidbodyto the component for moving it later. - Create a custom component
Projectileand add it to the prefab.
Now that we have our prefab setup we need to write the code that will actually handle the projectile itself. Once again this script is mainly focused on the poolable system itself not an actual working projectile.
using LMirman.Utilities;
using UnityEngine;
using Random = UnityEngine.Random;
[RequireComponent(typeof(Poolable))]
public class Projectile : MonoBehaviour
{
private Rigidbody rb;
private Poolable poolable;
private float destroyTimer;
private void Awake()
{
poolable = GetComponent<Poolable>();
rb = GetComponent<Rigidbody>();
poolable.ObjectRetrieved += PoolableOnObjectRetrieved;
}
private void FixedUpdate()
{
if (poolable.IsActive)
{
destroyTimer -= Time.deltaTime;
if (destroyTimer < 0)
{
poolable.Return();
}
}
}
private void OnCollisionEnter(Collision collision)
{
poolable.Return();
}
private void OnTriggerEnter(Collider other)
{
poolable.Return();
}
private void PoolableOnObjectRetrieved()
{
destroyTimer = 2.5f;
rb.velocity = Vector3.up * Random.Range(1f, 10f);
rb.angularVelocity = Vector3.zero;
}
}The last thing we need to do now is create an emitter for our projectiles. In the scene create a new empty game object and add a custom component ProjectileEmitter to it.
This emitter will use the ObjectPool.Instantiate function when the user left clicks to recycle inactive objects into the scene or create a new one if there are none waiting.
using LMirman.Utilities;
using UnityEngine;
public class ProjectileEmitter : MonoBehaviour
{
public GameObject projectileTemplate;
private Camera mainCamera;
private void Awake()
{
mainCamera = Camera.main;
}
public void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 position = mainCamera.ScreenToWorldPoint(Input.mousePosition);
position.z = 0;
ObjectPool.Instantiate(projectileTemplate, position, Quaternion.identity);
}
else if (Input.GetMouseButtonDown(1))
{
ObjectPool.DisposeAllIdlePools();
}
}
}With the Poolable Projectile prefab assigned to the Emitter projectileTemplate enter play mode and left click in the game window. Looking in the Hierarchy you can see the projectiles being created and reused.
Sometimes you want to prevent the Pool of a certain type of object from getting to large. In order to do so each Poolable has a Limit Capacity Limit Behavior which defines what it should do when the pool has reached a certain capacity. The behaviors are as follows:
-
None- Will never automatically manage the capacity of the pool. -
Dispose on Return- After an object is returned: if the inactive object pool is too large it will then be immediately disposed. -
Retrieve Oldest Active- When the number of active/inactive objects is reached: instantiating a new object will immediately retrieve the oldest active object without returning it to the pool. -
Recycle Oldest Active- When the number of active/inactive objects is reached: instantiating a new object will immediately return it to the pool and then retrieve it. -
Reject Population- When the pool is at capacity, do nothing and return null to the instantiation request. -
Throw Exception- When the pool is at capacity, throw an exception on instantiation request.