-
Notifications
You must be signed in to change notification settings - Fork 15
Object Pooling
antfarmar edited this page Nov 15, 2015
·
31 revisions
#Object Pooling
- Object pooling is a relatively common practice in games.
- Reusing GameObjects instead of destroying and instantiating them again saves limited CPU cycles.
####Unity Live Training: Object Pooling Link
- A lot of free scripts and tutorials on the subject.
- Unity has provided one in a Live Training session.
- A great introduction, not really production ready.
- Improvable.
3 basic scripts:
- A script which recycles a bullet after a period of time.
- A script which rapidly fires bullets (which are reused from the object pool).
- A script which manages the object pool itself.
using UnityEngine;
using System.Collections;
public class BulletDestroyScript : MonoBehaviour
{
void OnEnable ()
{
Invoke("Destroy", 2f);
}
void Destroy ()
{
gameObject.SetActive(false);
}
void OnDisable ()
{
CancelInvoke("Destroy");
}
}using UnityEngine;
using System.Collections;
public class BulletFireScript : MonoBehaviour
{
public float fireTime = 0.05f;
void Start ()
{
InvokeRepeating("Fire", fireTime, fireTime);
}
void Fire ()
{
GameObject obj = ObjectPoolerScript.current.GetPooledObject();
if (obj == null)
return;
// Position the bullet
obj.SetActive(true);
}
}using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ObjectPoolerScript : MonoBehaviour
{
public static ObjectPoolerScript current;
public GameObject pooledObject;
public int pooledAmount = 20;
public bool willGrow = true;
List<GameObject> pooledObjects;
void Awake ()
{
current = this;
}
void Start ()
{
pooledObjects = new List<GameObject>();
for (int i = 0; i < pooledAmount; ++i)
{
GameObject obj = (GameObject)Instantiate(pooledObject);
obj.SetActive(false);
pooledObjects.Add(obj);
}
}
public GameObject GetPooledObject ()
{
for (int i = 0; i < pooledObjects.Count; ++i)
{
if (!pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i];
}
}
if (willGrow)
{
GameObject obj = (GameObject)Instantiate(pooledObject);
pooledObjects.Add(obj);
return obj;
}
return null;
}
}- The bullet firing and recycling scripts are just an example of how to use the pooling system.
- The
ObjectPoolerScriptholds the important stuff, and there are several areas for improvement.
####Reusability
- The script itself was separated into its own component in order to be reusable.
- It is really only reusable between different projects.
- It is not reusable within the same project:
- The script only holds a reference to a single prefab.
- If you wanted to pool different types of bullets, powerups, or enemies, etc, you would need to use a new script for each one.
- Note that you couldn’t simply add this component multiple times and assign different prefabs:
- The class somewhat implements the singleton design pattern.
- Whichever script was the last one to Awake would have the static class reference called “current”.
- The other instances would not have a good way to be found or differentiated from each other.
- The system can pre-populate the object pool.
- It can grow where necessary (infinitely though, and List probably doubles in size after capacity met).
- Needs upper limits on growth.
- An object is considered “pooled” or “not pooled” based on whether or not its GameObject is active in the scene hierarchy.
- This is problematic for multiple reasons:
- The object is not made active before providing it to a consumer, and there is no way to know when a consumer will activate its object.
- This means that the pool might erroneously provide the same object to multiple consumers.
- The object is not made active before providing it to a consumer, and there is no way to know when a consumer will activate its object.
- Because the pool is checking activeInHierarchy, any parent object which is disabled will cause the pooled object to become marked as reusable – which may have been an unintended and unexpected consequence.
- The entire ancestral hierarchy has to be checked to determine whether or not an object is available for use – much slower than having a boolean somewhere.
- Never checks the validity of its own pooled items.
- e.g. If you have a pool of objects, a consumer takes the object and parents it to another object, and then that parent object is destroyed, you wont be able to save the pooled object from destruction. The pool manager will then crash the next time it checks the index of the destroyed object. In a system where you pull objects out of a pool and add them back in, the pool could choose not to add objects which are null, and therefore add a degree of safety.
####Collections Used
- Use a generic Queue<>: O(1) operations.
- A Queue’s Enqueue and Dequeue methods are both O(1) operations regardless of the size of the system.
- List<> would be somewhere between O(1) and O(n) depending on how quickly the object found a valid reusable object.
- Though RemoveAt from the end of a list in O(1) time as well.