Sign in to follow this  
  • entries
    17
  • comments
    38
  • views
    22078

The GameObject Pooler

Sign in to follow this  

1174 views

So another fellow was doing a journal and was a little nervous about posting code for review. It got me thinking that I should post some code up as well. (I do have a game update that I could do as well, but I'll save that for later)

Anyway, my game spawns a lot of random levels that are populated with random amounts of game objects, and rather than use GameObject.Instantiate and Destroy repeatedly, I decided to pool the objects and re-use them. I looked around online for free ones, saw someone posted theirs, but... I didn't like it, but it did give me a good base to strip out and make my own:[code=js:0]//This is a heavy modification of the original code found here: http://forum.unity3d.com/threads/simple-reusable-object-pool-help-limit-your-instantiations.76851/using UnityEngine;using System.Collections.Generic;public class GameObjectPooler : MonoBehaviour{ // TODO: I could probably re-work this to get rid of the singleton public static GameObjectPooler Current; //A public static reference to itself (make's it visible to other objects without a reference) public GameObject[] prefabs; //Collection of prefabs to be pooled HashSet prefabSet = new HashSet(); // internally we use this, makes it easier to programmatically add other prefabs to the pooler if we want Dictionary> pooledObjects; // key = prefab, value = list of pooled objects Dictionary instancedPoolObjects; // Key = instance, Value = prefab public int[] amountToBuffer; //The amount to pool of each object. This is optional public int defaultBufferAmount = 10; //Default pooled amount if no amount abaove is supplied GameObject containerObject; //A parent object for pooled objects to be nested under. Keeps the hierarchy clean void Awake() { //Ensure that there is only one object pool if (Current == null) { Current = this; } else { Debug.LogError("Attempting to spawn a second GameObjectPooler!"); Destroy(gameObject); } //Create new container containerObject = new GameObject("ObjectPool"); //Create new list for objects pooledObjects = new Dictionary>(); instancedPoolObjects = new Dictionary(); int index = 0; foreach (GameObject objectPrefab in prefabs) { int bufferAmount; if (index < amountToBuffer.Length) bufferAmount = amountToBuffer[index]; else bufferAmount = defaultBufferAmount; AddPrefab(objectPrefab, bufferAmount); // Go to the next prefab in the collection index++; } } public void AddPrefab(GameObject prefab) { AddPrefab(prefab, defaultBufferAmount); } public void AddPrefab(GameObject prefab, int startPoolSize) { if(!prefabSet.Add(prefab)) { Debug.LogError("Trying to add prefab that already exists in the GameObjectPooler"); } pooledObjects.Add(prefab, new Stack()); for (int i = 0; i < startPoolSize; i++) { CleanPoolObject((GameObject)Instantiate(prefab)); } } public GameObject GetObject(GameObject objectType) { Debug.Assert(pooledObjects.ContainsKey(objectType), "objecttype: " + objectType.ToString() + " not found in pool, did you add it to the GameObjectPooler?", objectType); GameObject returnObj = null; //If there are any left in the pool... if (pooledObjects[objectType].Count > 0) { GameObject pooledObject = pooledObjects[objectType].Pop(); pooledObject.transform.parent = null; returnObj = pooledObject; } else // I am just always allow growing, TODO: Grow by more than 1 at a time. { GameObject obj = Instantiate(objectType) as GameObject; returnObj = obj; } if(!instancedPoolObjects.ContainsKey(returnObj)) { instancedPoolObjects.Add(returnObj, objectType); } returnObj.SetActive(true); return returnObj; } public void PoolObject(GameObject obj) { CleanPoolObject(obj); Debug.Assert(instancedPoolObjects.ContainsKey(obj), "Object not found in pool: " + obj.name, obj); pooledObjects[instancedPoolObjects[obj]].Push(obj); instancedPoolObjects.Remove(obj); // this causes a bit of churn, but is handy for keeping track of all instances over pooled } // Be very careful with this. If anyone is holding any references to a pooled object, things get wonky public void PoolAllObjects() { foreach(var key in instancedPoolObjects.Keys) { CleanPoolObject(key); pooledObjects[instancedPoolObjects[key]].Push(key); } instancedPoolObjects.Clear(); } private void CleanPoolObject(GameObject obj) { //obj.SendMessage("OnClean"); // OnDisable gets called intrinsically obj.SetActive(false); obj.transform.parent = containerObject.transform; }}

Sign in to follow this  


4 Comments


Recommended Comments

When I first read your code, I thought you should make your class a Generic, something like:


// I just typed this, I didn't run it through a compiler. 
// I leave getting this working as an exercise to the reader...
GameObjectPooler <GeameObjectType> : MonoBehaviour where GameObjectType : MonoBehaviour
{
     List<GameObjectType> pooledObjects;

     public void AddPrefab(GameObjectType prefab)
     {

     }
     // etc. etc.
}
Then you'd have strongly typed values stored and wouldn't have to cast them to their real types. But after thinking about it, I don't know which is better for you.

I'm also not sure you want your Pooler to inherit from MonoBehaviour (or if Unity supports generic MonoBehaviour scripts). Instead the pooler could be a regular class that's contained by a different component.

Just something to think about.

Share this comment


Link to comment

Thanks for the feedback!  I could implement a generic method for the GetObject<GameObjectType> for those instances where I want to return the specific type.  That might be an easy win as far as modifications go, though I could see other games where having separate pool management might be the better way to go.

 

Good point on the possible removal as a monobehaviour.  I could easily stick this in my GameManager class and get rid of one behavior. If I was going for separate pool managers per object spawner, that would make even more sense.  (LaserRifle could own the pool of laserbeams)  Though I do lose being able to access the pooler directly from the Editor, but I could expose that functionality in the class that holds the pool.

 

(Though thinking on it, a static pool for a class like LaserRifle could be the way to go, probably depends on the game.  Might be a bit more of a nightmare if Unity was multi-threaded, but it's not, so don't have to worry about it)

 

Honestly, if I was going for more sustainable/reusable code without singletons, I probably should go with a pool<t> for each thing that spawns.  It would be a bit more work to do the PoolAll(), but not a huge amount.  And that is functionality that most other game types probably wouldn't need.

 

My tank tactics game, funnily enough, I didn't need an object pooler, at least yet.  Tanks only shoot one shell at a time =)

 

And thanks again for the feedback, as you can see, it got me thinking, which is always good.

Share this comment


Link to comment

Food for thought, yeah. I don't use Unity but it's interesting to see how you're grappling with static vs dynamic types & pooling. These same issues come up everywhere.

Share this comment


Link to comment

Yup, everywhere.  Random anecdote:  Back when I was working on a PS2 game, we had very strict memory budgets for each level.  I think it was something like 7MB.  Then about halfway through development the programmers bumped it to 9MB.  Why?   Well someone had declared a static 2MB buffer that no one was actually using.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now