This page explains how to use MMTools' object pooling system to reuse objects instead of instantiating and destroying them.

Introduction

Instantiating and destroying objects at runtime (bullets, particles, pickups, damage numbers…) has a cost, and generates garbage that will eventually trigger the garbage collector, causing hiccups. Object pooling solves that by creating a set of objects up front, and then reusing them: instead of instantiating, you grab an inactive object from the pool and enable it, and instead of destroying it, you simply disable it, and it goes back to the pool.

MMTools comes with a simple yet flexible pooling system, in MMTools/Core/MMObjectPool. Feel uses it internally (the MMFloatingTextSpawner relies on it, for example), and you can use it for your own objects.

The poolers

There are two poolers, both extending the MMObjectPooler base class:

MMSimpleObjectPooler

Pools a single type of object. Add it to an empty object in your scene, and set its:

  • GameObjectToPool: the prefab to pool
  • PoolSize: the number of instances to create on Awake
  • PoolCanExpand: whether new instances can be added to the pool if all existing ones are in use. If false, GetPooledGameObject will return null when the pool is depleted

MMMultipleObjectPooler

Pools many types of objects at once, and hands them out based on a PoolingMethod:

  • OriginalOrder: one of each, in the order of the list, regardless of their pool sizes
  • OriginalOrderSequential: goes through the list in order, handing out as many objects of each entry as its pool size before moving on to the next one
  • RandomBetweenObjects: a random entry, each with equal chances, regardless of pool size
  • RandomPoolSizeBased: a random entry, the larger its pool size, the higher its chances to get picked

Each entry of its Pool list has its own GameObjectToPool, PoolSize, PoolCanExpand, and an Enabled toggle (that you can also change at runtime via EnableObjects(name, status)). In random modes, CanPoolSameObjectTwice lets you prevent getting the same object twice in a row. You can also get an object of a specific entry via GetPooledGamObjectAtIndex(index) or GetPooledGameObjectOfType(name).

Common settings

  • MutualizeWaitingPools: if true, poolers pooling the same object will share a single pool instead of creating their own. For the multiple pooler, poolers with the same MutualizedPoolName will share their pool.
  • NestWaitingPool: if true, all pooled objects will be nested under an empty game object, to keep your hierarchy clean
  • NestUnderThis: if true, that waiting pool object will be nested under the pooler itself

Getting an object from the pool

Pools get filled on Awake. To get an object, call GetPooledGameObject(). It returns an inactive object, so it’s up to you to position it, and then enable it:

using MoreMountains.Tools;
using UnityEngine;

public class SimpleShooter : MonoBehaviour
{
	public MMSimpleObjectPooler BulletPooler;

	public virtual void Shoot()
	{
		GameObject bullet = BulletPooler.GetPooledGameObject();
		if (bullet == null)
		{
			return; // the pool is depleted and can't expand
		}
		bullet.transform.position = this.transform.position;
		bullet.transform.rotation = this.transform.rotation;
		bullet.SetActive(true);
	}
}

Returning an object to the pool

There’s nothing to “return”: a pooled object is considered available as soon as it’s inactive in the hierarchy. So instead of calling Destroy on it, simply disable it:

this.gameObject.SetActive(false);

Make sure you reset its state when it gets enabled again (in its OnEnable, for example), as a reused object keeps whatever values it had when it got disabled (velocity, health, color, etc).

MMPoolableObject

You can optionally add a MMPoolableObject component to your pooled prefabs. It brings a few helpers:

  • LifeTime: if above zero, the object will automatically disable itself (and go back to the pool) after that duration
  • Destroy(): a method that disables the object, so you can keep calling “Destroy” in your code without actually destroying anything
  • ExecuteOnEnable / ExecuteOnDisable: UnityEvents triggered when the object gets enabled (pulled from the pool) and disabled (returned to it)
  • OnSpawnComplete: a C# event you can trigger from your spawner via TriggerOnSpawnComplete(), once you’re done positioning and setting up the object
  • it also computes the object’s bounds (from its collider, collider2D or renderer), which you can access via its Size property, handy to spawn objects without overlaps

Good to know

  • Pooled objects are created when the pooler awakes, so very large pools will have a cost at scene start. Pick a pool size that matches your peak usage, and let it expand if needed.
  • Pools are recreated when a new scene gets loaded.
  • Never call Destroy on a pooled object, or the pool will lose it (and recreate one if it can expand).