If you have a game and you create and destroy GameObjects at a very high frequency and you notice performance going down then maybe you should consider using an object pool.
Today we’ll go over an implementation for an object pool to be used in Unity. You’ll be able to use this class to create pools of any type of GameObject, from pools of bullets to enemies, or environment objects.
Reasoning Behind An Object Pool
The reasoning behind this pattern is to improve performance by removing the need for instantiating and destroying game objects at a high frequency. Though this comes at the expense of memory.
Pattern
The pattern works this way. You pre initialize and create a pool of objects to be used. Whenever you need an object you ask the pool to lend you one. When the object is done serving its purpose instead of destroying it you place it back into the pool to be reused in the future.
When should you use pools?
- When objects frequently get created and destroyed. Think about particles in a particle system, or bullets in a bullet hell shooter game.
- When creating an object is really expensive.
Pattern Limitations
There is a tradeoff in using this pattern. One of the big ones is memory. Objects in a pool will take up space in memory whether or not they’re being used. Some objects in a pool may also be unnecessary so it’s a good idea to fine tune your pool to only hold the maximum number of objects to be used concurrently.
Pattern Implementation Ideas
There are a number of implementation details to figure out first. Here’s a couple
- Should an object know that it’s part of a pool?
One way to implement a pool is for a pool to continuously check up on individual objects. When one is not in use it can then be reclaimed. But this seems like a little too much work for the pool. The pools job should only be to lend out objects to whoever needs them. But what if the objects know that they’re part of a pool? - How should a game object be put back in a pool. From within the pool or outside
As we build our object pool we’ll answer these questions.
So once you’re done what can you expect? Well, you’ll be able to create any type of object pool with a line or 2 of code, as long as the object is a Unity GameObject.
Object Pool Code
Lets start to create our object pool. Lets also decide on a couple things about our pool.
- First our pool will be a generic pool as long as the object inherits from MonoBehaviour
- Pool objects should have a method used to place themselves back into the pool (move the burden away from the ObjectPool)
Lets create the IPoolable interface that Objects will need to follow if they’re going to be pool objects. This function will be used to set a delegate that’ll be called when the object is done being used. Skip to the bottom for an example and a quickstart on how to make and use this pool object implementation.
IPool Interface Code
public delegate void ReturnObjectToPool(GameObject objectToReturn); public interface IPoolable { void SetReturnToPool(ReturnObjectToPool returnDelegate); }
Next lets create the ObjectPool.
using System.Collections.Generic; using UnityEngine; public class ObjectPool<T> where T : MonoBehaviour, IPoolable { private Stack<T> _objectsNotInUse; private int _numberOfObjectsToSpawnWhenEmpty; private GameObject _objectPrototype; public ObjectPool(GameObject _objectPrototype, int startingNumberOfObjects, int numberOfObjectsToAddWhenEmpty) { _objectPrototype = _objectPrototype; _numberOfObjectsToSpawnWhenEmpty = numberOfObjectsToAddWhenEmpty; _objectsNotInUse = new Stack<T>(numberOfObjectsToSpawn); AddObjectsToPool(numberOfObjectsToSpawn); } public void HandleReturnToPool(GameObject objectToReturn) { objectToReturn.SetActive(false); _objectsNotInUse.Push(objectToReturn.GetComponent<T>()); } public T GetObjectFromPool() { if (_objectsNotInUse.Count == 0) { AddObjectsToPool(_numberOfObjectsToSpawnWhenEmpty); } T projectileToReturn = _objectsNotInUse.Pop(); projectileToReturn.gameObject.SetActive(true); return projectileToReturn; } private void AddObjectsToPool(int amountToAdd) { for (int i = 0; i < amountToAdd; i++) { GameObject newGameObject = Object.Instantiate(_objectPrototype, Vector3.zero, Quaternion.identity); T poolObject = newGameObject.GetComponent<T>(); poolObject.SetReturnToPool(HandleReturnToPool); _objectsNotInUse.Push(poolObject); // turn game object off newGameObject.SetActive(false); } } }
Example of Using the Object Pool Class
Once you have written or copied these scripts you can use the example below as a quickstart. First declare and instantiate a PoolObject with the 3 parameters.
- A GameObject prefab to create a pool out of
- An integer for the start number of objects
- An integer for the number of objects to add to the pool once the pool is empty (You can fine tune both these ints for your specific pool)
Down below we have the BadGuySpawner that creates the pool and further down we have the BadGuy script that we will be making a pool out of. Note that you only really need the GetObjectFromPool method and the constructor to get started!
using UnityEngine; public class BadGuySpawner : MonoBehaviour { public GameObject _badGuyPrefab; private ObjectPool<BadGuy> _enemyPool; private void Start() { // instantiate an object pool with a start size of 10 _enemyPool = new ObjectPool<BadGuy>(_badGuyPrefab, 10, 2); } public void SpawnBadGuy() { // grab a bad guy from the pool and set default values BadGuy badguy = _enemyPool.GetObjectFromPool(); badGuy.SetDefaultSpawnValues(); } }
using UnityEngine; public class BadGuy : MonoBehaviour, IPoolable { private int _health; private ReturnObjectToPool _returnDelegate; private void SetDefaultSpawnValues()() { _health = 2; transform.position = Vector3.zero; } public void SetReturnToPool(ReturnObjectToPool returnDelegate) { _returnDelegate = returnDelegate } public void Die() { _returnDelegate(gameObject); } }
If you guys use these scripts drop a link to your project down below! Stay tuned for the next post, I’ll go over a new Sprite Trail Renderer asset for 2D games that I’ll be releasing!