Search Unity

Stop wasting memory: recycle your objects!

Discussion in 'Scripting' started by spk, Nov 15, 2009.

  1. spk

    spk

    Joined:
    Jan 9, 2009
    Posts:
    529
    While working on my top-down shooter game, I noticed that the game would hang a bit periodically, due to the garbage collector cleaning up all these projectiles and enemies I had created over the last minute.

    In order to optimize this a bit, I created an object caching/pooling system that reuses objects instead of destroying them and allocating new ones later. This is ideal for game objects that have short lifespans and high frequency, such as projectiles and enemies.

    I've attached a package that contains the script files needed to use my ObjectPoolManager. I've included explanation and usage examples in the comments of the the ObjectPoolManager.cs file.

    For those who can't bother downloading the package now, here's the copy paste:

    I hope this will be of some use to someone :) Please let me know if you find any problems with it.

    Enjoy!
     

    Attached Files:

  2. lazalong

    lazalong

    Joined:
    Oct 13, 2009
    Posts:
    138
    Nice - I will certainly use it.

    Two suggestions:

    - InitialPoolSize: when a new GameObject type is created the manager creates x instantiations.
    - MaxPoolSize: if the max is reached then no more "bullets" are created. This could be used to limit memory consumption. Perhaps a MaxPoolSize could be stored per GO type.
     
  3. KHopcraft

    KHopcraft

    Joined:
    Jun 6, 2009
    Posts:
    3,246
    Very nice indeedy.

    Should this not be in the showcase forum?
     
  4. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Fully agree with above writters, nice for people with no background looking for something to start with :)

    No, more in the wiki
     
  5. spk

    spk

    Joined:
    Jan 9, 2009
    Posts:
    529
    Lazalong: yeah I thought about this as well, but I didn't need it for my game so I haven't bothered. Could make a "PoolConfiguration" component that one has to add to the ObjectPoolManager, for each object type he wants to pre-cache, with the following properties:
    - object prefab
    - default pool size
    - max pool size

    With this approach, you keep this system non-invasive as you don't need to add any special component on the target objects for it to work, yet you can still configure the manager on a per-prefab basis to fine-tune it the way you want.

    I might add this little feature later and post it on the wiki, as a previous poster suggested.
     
  6. rocket5tim

    rocket5tim

    Joined:
    May 19, 2009
    Posts:
    227
    When I try to add the the package to a new/empty project in Unity iPhone 1.5, Unity crashes. Maybe you could just post the scripts not in a package?
     
  7. kodagames

    kodagames

    Joined:
    Jul 8, 2009
    Posts:
    488
    Hello,

    Can someone help me to understand how to call the ObjectPoolManager? I just keep getting Unknown Identifier?

    Also from above it seems like this line of code is accessing another script or function within in this script. The part that has me baffled is the <Bullet>() Im guessing its calling the Bullet function but I cant get past the first error to see.
    Code (csharp):
    1.  
    2. Projectile bullet = ObjectPoolManager.CreatePooled( bulletPrefab.gameObject, position, rotation ) ).GetComponent<Bullet>();
    3.  
    Here is the Code Im using:
    Code (csharp):
    1.  
    2. var box : GameObject;
    3. var readynow : boolean = true;
    4.  
    5. function Update ()
    6. {
    7.     if(readynow)
    8.     {
    9.         MakeBox();
    10.     }
    11. }
    12.  
    13. function MakeBox()
    14. {
    15.      readynow=false;
    16.      box = ObjectPoolManager.CreatePooled(box.gameObject, transform.position, transform.rotation).GetComponent.GoodBye();
    17.      yield WaitForSeconds(2);
    18.      readynow=true;
    19.      GoodBye();
    20.        
    21. }
    22.  
    23. function GoodBye()
    24. {
    25.     //replace later with a OnCollision, this is just a test to see if I can get the pool working
    26.     //Destroy(gameObject, 5);
    27.     ObjectPoolManager.DestroyPooled( box.gameObject ); 
    28. }
    29.  
    The Original code was:
    Code (csharp):
    1.  
    2. var box : GameObject;
    3. var readynow : boolean = true;
    4.  
    5. function Update () {
    6.  if(readynow){
    7.   MakeBox();
    8.  }
    9. }
    10.  
    11. function MakeBox(){
    12.  readynow=false;
    13.  Instantiate(box, transform.position, transform.rotation);
    14.  yield WaitForSeconds(2);
    15.  readynow=true;
    16. GoodBye();
    17. }
    18.  
    19. function GoodBye()
    20. {
    21.  Destroy(gameObject, 5);
    22. }
    23.  
    All it does is create a box or cube with a rigidbody that falls from the sky every 2 seconds. I know that the Destroy part is being called wrong but this is just for testing to see the actual pool.

    Is there any chance you could put together a simple example So I can see how these (ObjectPoolManager) are actually being called in the code.

    It would be awesome if anyone could help.
     
    Last edited: Dec 9, 2010
  8. chaneya

    chaneya

    Joined:
    Jan 12, 2010
    Posts:
    401
    I would also like a little help with this.

    I'm not sure what Projectile is. Is that a separate class? And I have to agree with the previous post. I don't know what <Bullet> is. Is it the script you have associated to the bulletPrefab?

    A little help would be greatly appreciated.

    Allan
     
  9. spk

    spk

    Joined:
    Jan 9, 2009
    Posts:
    529
    Hey guys, sorry about the crappy doc... It's another example of rushed documentation! I was using some of my own classes for illustration purposes only (they were copy paste of my code) but this wasn't such a smart idea :)

    This is a typo in the doc. It should read Projectile which is a custom class I wrote for my project.

    Here's a simple example using Unity types only. Let's say you want to instantiate a new missile object that you pass as a GameObject prefab to your script (C#)

    Code (csharp):
    1.  
    2. public GameObject missilePrefab; // this is your prefab
    3.  
    4. void Start()
    5. {
    6.     GameObject myMissileInstance = ObjectPoolManager.CreatePooled( missilePrefab, transform.position, transform.rotation );
    7. }
    8.  
    Now, if you're passing in your prefab as a more specific type (e.g. a custom HomingMissile script that you have made) then you would use GetComponent() on the GameObject returned by ObjectPoolManager.CreatePooled() to access your HomingMissile component in your new missile instance:

    Code (csharp):
    1.  
    2. public HomingMissile missilePrefab; // here you're asking for a specific type of prefab that MUST contain your HomingMissile component
    3.  
    4. void Start()
    5. {
    6.     HomingMissile myHomingMissileInstance = ObjectPoolManager.CreatePooled( missilePrefab.gameObject, transform.position, transform.rotation ).GetComponent<HomingMissile>();
    7. }
    8.  
    You could also write the Start() method above in more steps like this:
    Code (csharp):
    1.  
    2. void Start()
    3. {
    4.     GameObject newMissileObject = ObjectPoolManager.CreatePooled( missilePrefab, transform.position, transform.rotation );
    5.  
    6.     HomingMissile myHomingMissileInstance = newMissileObject.GetComponent<HomingMissile>();
    7. }
    8.  
    Since this is C# code, you will have to do the javascript conversion yourself but this should be straightforward. Let me know if you're having troubles with it.

    Here's another example taken from my latest game Bugfest. It's a utility function I use to create a new bug instance from a prefab.
    Code (csharp):
    1.  
    2.  
    3. public Bug Spawn( Bug prefab, Vector3 pos, Collider goal )
    4.     {
    5.         Bug bug = ObjectPoolManager.CreatePooled( prefab.gameObject, pos, transform.rotation ).GetComponent( typeof( Bug ) ) as Bug;
    6.         Initialize( bug );
    7.         bug.Spawn();
    8.         return bug;
    9.     }
    10.  
    11.  
     
    Last edited: Jan 13, 2011
  10. chaneya

    chaneya

    Joined:
    Jan 12, 2010
    Posts:
    401
    Spk,

    Thanks. That did it for me.

    Zeek,

    If you're still checking out this thread and can't figure this out, let me know.

    Allan
     
  11. JAMiller

    JAMiller

    Joined:
    Apr 2, 2009
    Posts:
    73
    Hello, I just noticed my game is experiencing hitches in frame rate due to garbage collection. I have a lot of very quick particles being created and destroyed and I am happy to find this system! Can't believe it was only posted 2 days ago!

    Seems easy enough to use, except I am having one problem that is preventing it from working. I can't seem to access ObjectPoolManager as a type like that. Why can't I access it like I do my static functions made in JavaScript?. Is it because it is in C# and I am using JavaScript? The error I get is:
    I can get around this by making a GameObject variable for the editor and drag-drop a global object with the ObjectPoolSystem on it, for every object in my game that needs it, then grab the component off that to use the functions. Meaning something like:
    Is that the only way?

    Sorry I'm not an very experienced programmer with advanced stuff like this (Which is why I'd love to take advantage of your system here!)

    Any help would be appreciated!
     
    Last edited: Jan 18, 2011
  12. Chris Sinclair

    Chris Sinclair

    Joined:
    Jun 14, 2010
    Posts:
    1,326
  13. JAMiller

    JAMiller

    Joined:
    Apr 2, 2009
    Posts:
    73
    That worked, thanks! I didn't know that about JavaScript and C#. Seems to be working now, I'm pretty sure (even got a nice debug warning when I tried to delete a non-pooled object accidently).

    Seems to have helped the spikes in garbage collection quite a bit, I wish I would've tracked the numbers better... but the graph definitely looks better.
     
  14. JAMiller

    JAMiller

    Joined:
    Apr 2, 2009
    Posts:
    73
    One more question, should I be seeing the objects remaining in the inspector window after I think they are destroyed? I'm not sure if the DestroyPooled is going through, since I see leftover object(clone)s in the scene (or if those are now pooled objects waiting to be reused)
     
  15. Aaron

    Aaron

    Joined:
    Oct 22, 2008
    Posts:
    122
    First of all i want to thank SPK for a wonderful script! It works very well after i've fiddled around and got it implemented properly. It did take some time to figure it out though so I will post a couple tips for future users of this script! This was implemented in java using 2 separate scripts.

    I'll show you an example of instancing an animated effect to show wether the Player has been hit
    ________________________________________________________________________________

    Script 1.) PlayerControls.js

    var getHit : GameObject // This is the prefab you want to instantiate that holds a script to play an animated hit effect



    function OnTriggerEnter(getCollider : Collider){

    if(getCollider.tag == "Enemy"){//CREATES THE HIT EFFECT

    var CreateHitEffect : GameObject = ObjectPoolManager.CreatePooled( getHit, transform.position, getHit.transform.rotation ) as GameObject;

    }//END ENEMY HIT CHECK


    }//END TRIGGER


    ____________________________________________________________

    The next part of the implementation (clearing the object from the scene in the editor until its used again)

    Script 2.) CreateHit.js

    //THIS IS THE SCRIPT RUNNING ON THE PREFAB

    var hitCount : int;
    var hitSpeed : float;
    var hitIterate : int;

    var offset : Vector2;
    //END VARIABLE DECLARATION

    //***YOU HAVE TO RESET YOUR SCRIPT VARIABLES TO DEFAULT DURING START OR IT WILL RETAIN THE LAST SET VALUES BEFORE YOU DESTROY POOLED***

    function Start(){

    offset.x = 0;
    renderer.material.SetTextureOffset ("_MainTex", offset);

    hitCount = 0;
    hitIterate = 0;
    hitSpeed = 1;

    }

    function AnimateHit(){//HANDLES THE ANIMATION OF THE HIT EFFECT

    if(hitCount <= hitSpeed){
    ++hitCount;
    }



    if((hitCount >= hitSpeed) (hitIterate == 0)){
    offset.x = 0.2;
    renderer.material.SetTextureOffset ("_MainTex", offset);
    hitCount = 0;
    hitIterate = 1;
    }


    if((hitCount >= hitSpeed) (hitIterate == 1)){
    offset.x = 0.4;
    renderer.material.SetTextureOffset ("_MainTex", offset);
    hitCount = 0;
    hitIterate = 2;
    }


    if((hitCount >= hitSpeed) (hitIterate == 2)){
    offset.x = 0.6;
    renderer.material.SetTextureOffset ("_MainTex", offset);
    hitCount = 0;
    hitIterate = 3;
    }


    if((hitCount >= hitSpeed) (hitIterate == 3)){
    offset.x = 0.8;
    renderer.material.SetTextureOffset ("_MainTex", offset);
    hitCount = 0;
    hitIterate = 4;

    }



    if((hitCount >= hitSpeed) (hitIterate == 4)){

    hitIterate = 5;
    ObjectPoolManager.DestroyPooled(transform.gameObject);//ANIMATION IS NOW DONE PLAYING, DESTROY POOLED

    }

    }//END ANIMATE SPLASH


    function Update () {

    AnimateHit();

    }
     
  16. spk

    spk

    Joined:
    Jan 9, 2009
    Posts:
    529
    Objects you destroy using DestroyPooled() should appear as disabled (grayed out) under the ObjectPool singleton object in the scene. If you still see your objects as active in the inspector and not as a disabled child of ObjectPool, it could be that you called DestroyObject() on a component instead of the root GameObject - eg: you used DestroyPooled( bullet ) instead of DestroyPooled( bullet.gameObject ).
     
  17. Rafes

    Rafes

    Joined:
    Jun 2, 2011
    Posts:
    764
    Hi all,

    I don't mean to hijack your thread or your hard work, but I wanted to let you know we just released a plugin called PoolManager. Check out all of the features and video here:
    PoolManager.path-o-logical.com

    I hope it helps!

    - Rafe
     
  18. yuriythebest

    yuriythebest

    Joined:
    Nov 21, 2009
    Posts:
    999
    hi! Is this syntax correct in JS if I want the object to destroy itself?

    Edit: yes, it is- can you please add a "max pooled objects" feature?
     
    Last edited: Aug 17, 2011
  19. bumble864

    bumble864

    Joined:
    Jan 27, 2011
    Posts:
    128
    SPK, Got an interesting question for you if your still looking at this thread. I'm making a 2D top down shooter style game, I've implimented this pool manager and I LOVE it so far (major kudos). Here's my problem, it "appears" the bullet maintains it's original world space coordinates at despawn, then when that bullet comes up in the game it respawns where it despawned THEN moves to the barrel. The problem is for that fraction of a second it's over a target and the target is registering a hit but I'm clear across on the other side of the screen. Any ideas? This has to be related to the pool manager because it's not doing that with standard Instantiate.
    Thanks for any advice
    Russ
     
  20. deadfire52

    deadfire52

    Joined:
    Jun 15, 2011
    Posts:
    101
    Sorry, I realize that this is kind of an old post but I've tried looking at the code and I can't find a solution.

    So here is how I instantiate bullets :

    Code (csharp):
    1. function StartFire(){
    2.     timer += Time.deltaTime;   
    3.     if(bullets[currentGun] > 0){
    4.         if (timer >= ShootDelay){
    5.             timer = 0;
    6.             Muzzel_Flash.renderer.enabled = true;
    7.            
    8.             var clone : GameObject = ObjectPoolManager.CreatePooled(Player_Bullet, Vector3(Bullet_Instantiator.transform.position.x,Bullet_Instantiator.transform.position.y-.01,Bullet_Instantiator.transform.position.z), Bullet_Instantiator.transform.rotation) as GameObject;
    9.             bullets[currentGun] --;
    10.             yield WaitForSeconds(.05);
    11.             Muzzel_Flash.renderer.enabled = false;
    12.         }
    13.     }
    14. }
    When I call this method, I always get the three errors in the screen shot.

    Double clicking on the first error brings up this line from ObjectPoolManager.cs
    Code (csharp):
    1. instance2pool[obj] = pool;
    $Erors.png