A quick way to use ObjectPooling in Unity

vectorfocus

Vector focus

Posted on February 12, 2024

A quick way to use ObjectPooling in Unity

In Unity, pooling is an essential method for reusing gameObjects.
This breaks the infernal loop of infinite destruction/creation.

The principle of object pooling is that, instead of destroying a gameObject, we deactivate it and put it in its initial state so that we can reuse it later.

For example, in a shooter, each explosion could be a gameObject that we recycle.

How to use it on Unity.

Since version 2021, Unity has had a specific method for managing gameObject pooling: ObjectPool.
This very efficient method has 7 parameters, see below for a real example:

using UnityEngine.Pool;

public class RocketManager : MonoBehaviour
{
    // prefab
    public Rocket prefabRocket;

    public ObjectPool<Rocket> PoolRocket { get; set; }

    public void Awake()
    {   
        PoolRocket = new ObjectPool<Rocket>(OnCreate, OnTake, OnReturn, OnDestroy, false, 10, 20);
    }

    private void OnCreate()
    {

    }

    private void OnTake(Rocket rocket)
    {

    }

    private void OnReturn(Rocket rocket)
    {

    }

    private void OnDestroy(Rocket rocket)
    {

    }
}
Enter fullscreen mode Exit fullscreen mode

It's true that these parameters are optional except for the first one, but this can quickly become very complicated for the readability of your code.
Especially if 80% of the time your actions (the first 4 parameters) do the same thing all the time.

A good starting point would be to create a service that allows us to avoid this repetition.

The quick-to-use version

In my case, I use pooling 90% of the time on gameObjects with their own Monobehaviour.
The service will work even if your gameObject doesn't have a custom Monobehaviour, you'll just have less control over the configuration.

To start we'll create an IRelease interface, which will have an implementable method for adding an action when the gameObject returns to the pool.
If it's implemented, its method will allow us to reset our Gameobject to its initial state, for example.

public interface IRelease
{
    void Release();
}
Enter fullscreen mode Exit fullscreen mode

Then, to avoid the hassle of specifying parameters for each new gameObject we want to pool, we'll create a static C# class.

In my case, most of the time the only parameters I needed to change were the parameters for the initial and maximum number of gameObjects or the collectionCheck.

using UnityEngine;
using UnityEngine.Pool;

public static class BaseObjectPooling
{
    public static ObjectPool<T> GetDefaultPooling<T>(T prefab, int initial = 10, int max = 20, bool collectionChecks = false) where T : MonoBehaviour
    {
        return new ObjectPool<T>(() => GameObject.Instantiate(prefab), (T obj) => obj.gameObject.SetActive(true), OnRelease, (T obj) => GameObject.Destroy(obj), collectionChecks, initial, max);
    }

    private static void OnRelease(MonoBehaviour obj)
    {
        IRelease release = obj as IRelease;

        if (release != null)
        {
            release.Release();
        }

        obj.gameObject.SetActive(false);
    }
}
Enter fullscreen mode Exit fullscreen mode

The code above shows that the instantiation, activation and deletion actions do nothing more than their primary actions.

There's just the OnRelease method, which will call an additional method if the IRelease interface is implemented.

How it's used

We're going to use another MonoBehaviour to initialise our Pool system.
Note that I always instantiate my pools in the Awake method, and then I never use them until the Start method is called.
This ensures that my pool is ready and avoids errors.

In the example below, we're going to set up a Pool system for rockets, using a prefab as a model.
The Rocket class is a MonoBehaviour, and we invoke 10 rockets using the pools, associating a random colour with them.
The manager is a singleton to make the demonstration easier, but it's not compulsory.

using UnityEngine.Pool;

public class RocketManager : MonoBehaviour
{
    // prefab
    public Rocket prefabRocket;

    public ObjectPool<Rocket> PoolRocket { get; set; }

    public static RocketManager Instance { get; private set; }

    public void Awake()
    {
        Instance = this;

        PoolRocket = BaseObjectPooling.GetDefaultPooling<Rocket>(prefabRocket);
    }

    private void Start()
    {
        for (int i = 0; i < 10; i++)
        {
            var instance = PoolRocket.Get();
            instance.SetColor(GetRandomColor());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this fictitious example, the rocket will have to return to its Pool when the rocket is destroyed.
This is illustrated by the "Explosion" method.

public class Rocket : MonoBehaviour, IRelease
{
    public void SetColor(Color color)
    {
        // set color
    }

    public void Explosion()
    {
        // this ship is destroy, back to the pool
        RocketManager.Instance.PoolRocket.Release(this);
    }

    public void Release()
    {
        // base color
        SetColor(Color.gray);
    }
}

Enter fullscreen mode Exit fullscreen mode

Well, the colours example doesn't really make sense, but it's there to show that Release can be used to reset a component.
This is not shown in this example, but it is possible to track undestroyed objects in a list.

This service isn't as functional as it could be, but it seems to me to be a good approach to using Pools quickly.
Feel free to customise it at home to make it more efficient =)

You can find more Unity tips and tricks on my blog.

💖 💪 🙅 🚩
vectorfocus
Vector focus

Posted on February 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related