Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    19
  • comments
    41
  • views
    23037

The GameObject Pooler

Sign in to follow this  
ferrous

1311 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
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!