Object Pooling

Since for Look Out Below! we’re targeting mobiles we want to avoid instantiating lots of objects while playing. There’s a few pooling systems out there but we’ve decided to roll out our own to suit us.

In look out below we use an extension of monobehaviour called ReusableInstance as the base class for all pooled objects, the cargo, level sections and effects all use this.

When normally we’d instantiate a prefab, we instead call GetUnusedInstance() on the prefab, this will return an instance already in the scene thats not being used, or instantiate one it there is none.

The main advantage of this system is that the prefabs manage the pooling themselves and they can be assigned in the inspector without extra thought, rather than having to go through any kind of manager, its all contained in an array that belongs to the prefab, it keeps references to clones of itself.

So this works great for getting the objects, the only thing that requires some extra thought is that we have to manually set the object as “not busy” for it to be reused, but I think this extra work is in most pooling systems, so I can live with that.

The systems been in place for a while now with no issues which is great, so yeah very easy to manage system, think I might add the code here later in an update if I clean it up a bit.

UPDATE:

Ok here’s the code we used for ReusableInstance in Look Out Below!, the entire game basically hangs on it, the tiles are dotted all around the level, reusable grannies etc. The most common usage was to set to isBusy = false when whatever is it has gone off screen.

Checkout the handy unityPackage below with a basic reusable bullet scene.
ReusableInstance

Or the code for ReusableInstance.cs below (included in the package but I thought I’d post it anyway)

ReusableInstance.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ReusableInstance : MonoBehaviour {

/// The instances of this prefab
[HideInInspector] public List instances = new List();

/// Bool detemining if this is a prefab or an in scene instance
private bool isFirstInstance = false;

/// Gets the cached transform. public for convenience
public Transform cachedTransform
{
get{if(_mCachedTransform == null){_mCachedTransform = transform;}return _mCachedTransform;}
}
Transform _mCachedTransform;

/// Gets or sets a value indicating whether this is busy.
[HideInInspector] public bool isBusy
{
get {return _mIsBusy;}
set{
_mIsBusy = value;
OnBusySet(value);
}
}
bool _mIsBusy;

/// Gets a parent for this gameobject when its not in use, creates it if nessecary for scene cleanliness
static Transform preInstantiatedParent{
get{
if (!m_PreInstantiatedParent){
m_PreInstantiatedParent = new GameObject("_ReusableInstances").transform;
m_PreInstantiatedParent.position = Vector3.down * 10000f; //position far offscreen
}
return m_PreInstantiatedParent;
}
}
static Transform m_PreInstantiatedParent;

/// Returns an instance that is not busy or creates a new one
public ReusableInstance GetUnusedInstance()
{
ReusableInstance toReturn = null;
if (instances != null && instances.Count != 0){
foreach (ReusableInstance instance in instances){
if (instance && !instance.isBusy){
toReturn = instance;
}
}
}
if (!toReturn){ toReturn = InstantiateNewInstance();}
toReturn.isBusy = true;
return toReturn;
}

/// Instantiates a new instance.
ReusableInstance InstantiateNewInstance()
{
ReusableInstance newInstance = (Instantiate(this.gameObject) as GameObject).GetComponent();
instances.Add(newInstance);
newInstance.name+=this.name;
return newInstance;
}

/// Clear the instance list
void ClearInstanceList()
{
instances.Clear();
}

/// Instantiates several instances at once (useful at the start of a level)
void PreInstantiate(int numberOfInstancesToCreate)
{
for (int i=0; i < numberOfInstancesToCreate; i++) { ReusableInstance ins = GetUnusedInstance(); ins.isBusy = true; ins.cachedTransform.position = Vector3.down * 1000f; ins.cachedTransform.parent = preInstantiatedParent; } foreach (ReusableInstance inst in instances) { inst.isBusy = false; } } /// Sets up a gameobject as the first instance in the cases where the original object is in a scene (i.e not prefabs in the project) void AddSelfAsFirstInstance() { if (!instances.Contains(this) && instances.Find((obj) => obj.name == this.name) == null){
instances.Clear();
instances.Add(this);
this.isBusy = false;
isFirstInstance = true;
}
else{
isFirstInstance = false;
}
}

/// Bool delegate. format void MyFunction(ReusableInstance instance, bool arg) called when the busy state changes
public delegate void BoolDelegate(ReusableInstance instance, bool arg);
public BoolDelegate onBusySet;

void OnBusySet(bool arg){
if (onBusySet!=null){onBusySet(this, arg);}
this.cachedTransform.parent = preInstantiatedParent;
this.cachedTransform.localPosition = Vector3.zero;
}
}

Leave a comment

Your email address will not be published.