Search Unity

Bullet Pool Firing; Improving Performance

Discussion in 'Scripting' started by renman3000, Jan 31, 2013.

  1. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Hi there,
    I have a system, in which I fire bullets. This system, on the iOS (A4 chip, A5 chip devices work fine) causes up to a 7 FPS drop in performance. I am wondering if I just list my basic steps if anyone can see if something I am doing could be done better.


    1. call bullets from a bullet pool.
    Basically, if a bullet is selected, but its renderer is enabled, I cycle thru the pool until I find one where renderer in disabled (not in use)
    Code (csharp):
    1.  
    2.     if(bullet.renderer.enabled == false)
    3.     {
    4.         fireBullet();
    5.         return;
    6.     }  
    7.    
    8.     if(bullet.renderer.enabled == true)
    9.     {  
    10.         for(var b : int = 0; b < BulletArray.Length; b++)
    11.         {
    12.             bullet = BulletArray[b];
    13.  
    14.             if(bullet.renderer.enabled == false)
    15.             {
    16.                 b = BulletArray.Length;
    17.                 fireBullet();
    18.                 return;
    19.             }
    20.         }
    21.     }
    22.  

    2. turn on the renderer, collider various others....
    Code (csharp):
    1.  
    2.     bullet.renderer.enabled = true;
    3.     bullet.collider.enabled = true;
    4.     bullet.collider.isTrigger = false;
    5.     bullet.rigidbody.WakeUp();
    6.     bullet.rigidbody.mass = 5;
    7.     bullet.layer = 15;
    8.     bullet.rigidbody.velocity = Vector3(0,0,0);
    9.  
    10.  

    3. Set the scale, position and rotation of the bullet
    Code (csharp):
    1.  
    2.     bullet.transform.position = activeNozzle.transform.position;
    3.     bullet.transform.rotation = activeBrain.transform.rotation;
    4.  
    5.     if(brainFlip == false)
    6.     {
    7.         if(bullet.transform.localScale.x < 0)
    8.         bullet.transform.localScale = Vector3(bullet.transform.localScale.x * -1, bullet.transform.localScale.y, bullet.transform.localScale.z);
    9.         if(bullet.transform.localScale.y < 0)
    10.         bullet.transform.localScale = Vector3(bullet.transform.localScale.x, bullet.transform.localScale.y * -1, bullet.transform.localScale.z);       
    11.     }
    12.     else if(brainFlip == true)
    13.     {
    14.         if(bullet.transform.localScale.x < 0)
    15.         bullet.transform.localScale = Vector3(bullet.transform.localScale.x * -1, bullet.transform.localScale.y, bullet.transform.localScale.z);       
    16.         if(bullet.transform.localScale.y > 0)
    17.         bullet.transform.localScale = Vector3(bullet.transform.localScale.x, bullet.transform.localScale.y * -1, bullet.transform.localScale.z);
    18.     }

    4. Determine the trajectory of the bullet based on two points with in the gun nozzle.
    Code (csharp):
    1.  
    2.     var aPt : Vector3;
    3.     aPt = Nozzle2.transform.position; // touch position
    4.     var bPt : Vector3;
    5.     bPt = Nozzle1.transform.position; //playerObject position in screen space
    6.     var nozzleRise : float;
    7.     nozzleRise = aPt.y - bPt.y; /// rise
    8.     var nozzleRun : float;
    9.     nozzleRun = aPt.x - bPt.x; /// run         
    10.     var bulletLocalTrajectory : Vector2;
    11.     bulletLocalTrajectory = Vector2(nozzleRun, nozzleRise);
    12.     bulletLocalTrajectory.Normalize();
    13.     activeBrainDeltaPos = Vector3.ClampMagnitude(activeBrainDeltaPos, 0.1f);
    14.  

    5. Apply velocity and swap image (image swap to create mini animation)
    Code (csharp):
    1.  
    2.     bullet.rigidbody.velocity = Vector3((bulletLocalTrajectory.x + activeBrainDeltaPos.x)* speed, (bulletLocalTrajectory.y + (activeBrainDeltaPos.y)) * speed, 0); ///shoot bullet, give it force.///notice x and y flipped due to rise and run.. not run and rise.
    3.     bullet.rigidbody.freezeRotation = true;    
    4.    
    5.     tgCount++;
    6.     if(tgCount == 1)
    7.     {
    8.         activeBrain.renderer.material.mainTexture = tgImage1;
    9.         return;
    10.     }
    11.    
    12.     else if(tgCount == 2)
    13.     {
    14.         activeBrain.renderer.material.mainTexture = tgImage2;
    15.         tgCount = 0;
    16.     }  
    17.  






    That is basically it. If you can see something that would glaringly improve basic performance, please let me know.
    *These functions are called by an invokeRepeat() at a frequency of 0.1 seconds.
     
    Last edited: Jan 31, 2013
  2. KyleOlsen

    KyleOlsen

    Joined:
    Apr 3, 2012
    Posts:
    237
    I can see one small thing, I'd cache those frequently accessed components of your bullets.

    Inside of bullet's Awake()
    Code (csharp):
    1.  
    2. void Awake()
    3. {
    4.     cachedTransform = GetComponent<Transform>();
    5.     cachedRigidbody = GetComponent<Rigidbody>();
    6.     cachedMeshRenderer = GetComponent<MeshRenderer>();
    7. }
    8.  
    Example use of cached transform, works just like before.
    Code (csharp):
    1.  
    2.  bullet.cachedTransform.position = Vector3.zero;
    3.  
     
  3. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    I am not sure what you mean.
    I have each bullet a meber of an array, handled by my weapons manager. In essence what I am doing is calling up each bullet, as my BulletCount int is increased by ++. So, bulletCount = 1 use bullet 1 of the array.

    What would caching them pre hand get me? I just don't see it's boost potential.

    ??
     
  4. KyleOlsen

    KyleOlsen

    Joined:
    Apr 3, 2012
    Posts:
    237
    Not caching your bullets, but caching frequently accessed components OF your bullets such as its transform or rigidbody. If your bullet array is an array of GameObjects, perhaps consider creating a custom class and attaching it to your bullet in order to store those frequently accessed components.

    Code (csharp):
    1.  
    2. bullet.transform;
    3. bullet.GetComponent<Transform>();
    4.  
    Both of those lines do the same thing and I'm sure you're aware of the performance cost of GetComponent.

    Unity Documentation showing this in practice: http://docs.unity3d.com/Documentation/ScriptReference/index.Performance_Optimization.html
     
    Last edited: Jan 31, 2013
  5. foxter888

    foxter888

    Joined:
    May 3, 2010
    Posts:
    530
    rather than turning off each component that the bullet has, why not just turn off the game object itself? because pretty much that would just disable everything
     
  6. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    @KyleOlsen,
    I am still struggling to comprehend, caching a transform and its benefit.

    I cache and store components scripts, but why cache a transform or rigidbody, when it is accessed very directly with bullet.transform or bullet.rigidbody. Maybe you could explain it a bit better? Sorry, I just don't get it.

    Thanks.


    @Foxter888
    True, will try that out? Do you know for experience if this is a performance saver? Seems like it might.


    And thanks!
     
  7. e5an

    e5an

    Joined:
    Jul 6, 2012
    Posts:
    93
    What's to understand? It works. Just do it.
     
  8. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    While using bullet.transform looks like you're just accessing a stored variable, it's actually doing some work behind the scenes to get the Transform (not actually a GetComponent any more, but still slower than just grabbing a variable). Storing these values yourself will be faster than referring to them that way.

    Also, depending on how many bullets are in the pool and how many of those are in use at one time, it may be faster to have an "in-use bullets" and "available bullets" pool and move objects between them, as opposed to searching through for ones that aren't in use each time.
     
  9. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    You dont need to manage in-use bullets. Completely pointless.

    You are however correct in saying you should only keep available bullets in the pool.

    My pooling system works like this... (please note names are completely different)

    PoolManager.GetMeABullet()

    PoolManager checks if it has any available. If yes, pass back, if not create new one.

    Projectile on end of life passes itself back to poolmanager
     
  10. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    @Errorsatz
    I see.
    So any pointer, then component, will take infinitely(?) longer than having that value stored. Awesome!
    I just thought transform was so universal, it was like nothing to call it. For this function, very useful. Thanks!!!

    Edit. But, another point raised was to simply disable the entire gameObject. Thoughts Vs stored components?


    @JamesLeeNZ
    Well, since I am building for mobile platforms, memory is key and to do any type of instantiation and destruction is key. As these two culprits are heavy on mobile.
     
    Last edited: Feb 1, 2013
  11. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    cache everything. It causes a stall because unity has to stop, and search with GetComponent each time. If you read the performance optimisation section on the docs you'd clearly see unity recommending you do exactly that. So don't argue, cache it :)

    Also individually disabling everything on the pooled object one by one... isn't that daft? just set go.active to true or false.
     
  12. KyleOlsen

    KyleOlsen

    Joined:
    Apr 3, 2012
    Posts:
    237
    Always listen to everything this hippo has to say. :)
     
  13. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    The problem with completely disabling a GameObject is that if it contains any scripts, when that object is re-enabled, all the caching of components (assuming you do it in Awake() or OnEnable() or any setup for that object will happen again which can be costly depending on what is going on.

    Typically, disabling the renderer and the collider is very inexpensive (provided) you cached a reference to both of them. It also doesn't mess up whatever scripts that might have been running on that object.

    BTW: The gains from caching components or object properties (,position for instance) can be huge especially compared to when you are accessing those components multiple times and many of them in update. Caching Components which are classes (Reference types) is typically done once in Awake(). For Structures (.position, .rotation, etc) which are value types since accessing them only give you a snapshot of what their values were when you accessed them don't make sense to cache there. However, if you are going to perform several operations on those in an update cycle, it still makes sense to cache them as accessing them is still costly.

    Code (csharp):
    1.  
    2. Transform myTransform;
    3.  
    4. void Awake()
    5. {
    6.      myTansform = transform; // Caching the transform component of the object.
    7. }
    8.  
    9.  
    10. void Update()
    11. {
    12. Vector3 pos = myTransform.position; // caching the position of the object by accessing the already cached transform.
    13.                                                    
    14. // Caching the position here only makes sense if we are going to perform several operations on that position.
    15.  
    16. }
    17.  
    18.  

    Anyway hope this helps.

    P.S. Was typing when Hippo posted ... ie. Tru that!
     
    Last edited: Feb 1, 2013
  14. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    @Hippo
    So, if, overall, I am not adjusting any component, just setting it to on of off, I am not adjusting mass to 20 here and 30 there, I am just saying, collider active, collider is trigger, renderer is enabled, etc, is setting object x.active or not, better and eliminate the need to cache transforms etc? #InquiringMindsWantToKnow

    ////...in other words, when running thru the pool, I check renderer is active. Is checking object.active possible?? #sorryVeryLateToTest



    @KyleOlson
    With a name like HippoCoder I must concur.


    @Stephen B
    So, when I cache, a transform, collider, rigidbody, etc, I should do this in Awake? I tend to cache at Start() but again, I suppose in hind sight this is a misnomer. Can you break down the classes for me which should be cached at Awake? Meaning Transforms, Colliders, RigidBodies, Scripts, Positions, which of these should not be cached on Awake if any?

    Thanks!!!
     
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Start isn't a misnomer. It's called when the object starts to do things. That's not the same as Awake, which is when it's initialised.

    The less work that you do in code, the better. Why change a dozen variables in a function if you can get the same effect by changing one? What's more, by modifying the state of a bunch of components you're potentially making the engine do a lot more work than when you just change the state of the container object.
     
  16. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I suggest you break out the old System.Diagnostics + Stopwatch() and test this out. It was an eye opener for me and well worth the investment of time.

    FYI: Just to give you an idea of what you might uncover... this is what I get on my PC.

    Code (csharp):
    1.  
    2.  
    3. private Transform myTransform;
    4. private Vector3 position;
    5.  
    6. void Awake()
    7. {
    8.      myTransform = transform; // Could have use GetComponent<Transform>();
    9. }
    10.  
    11.  
    12. void Start()
    13. {
    14.      // Each of the commented out lines below were tested one at a time over this loop.
    15.      position = myTransform.position; // Caching position if I ever needed to do several operations on the position.
    16.                                       // As you see below even if the transform is cached, you still get a hit from accessing .position.  
    17.  
    18.      for (int i = 0; i < 1000000; i++)
    19.      {
    20.  
    21.           //position = GetComponent<Transform>().position + new Vector3(1, 0, 0); // 165 ms
    22.           //position = transform.position + new Vector3(1, 0, 0); // 74.5ms
    23.           //position = myTransform.position + new Vector3(1, 0, 0); // 45ms
    24.           //position = position + new Vector3(1, 0, 0); // 18.4ms
    25.  
    26.      }
    27. }
    28.  
     
  17. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    @AngryPenguin,
    So, by a container, you mean what exactly? For instance, I have thisScript, which is on thisPlayer. thisPlayer I want to access his rigidbody, mass, layer, renderer, collider and trigger frequently. How do you proceeeed???? {- very ominous delivery. :)(?}


    @Stephan B,
    Wow.


    Cool . Certainly food for thought.
     
    Last edited: Feb 1, 2013
  18. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Hi guys,
    thanks for all the help. One question tho, how do I set the cahced members of an array? Meaning, I can set myTransform = transform in Awake, but how do I set bullet[1]Transform = bullet[1].transform? Do you see what I mean?

    I would need a myTransform var for each bullet. How do I differentiate? Thru another array of myTransform : Transform[]? Hope you see what I mean.


    so far I am leaning towards something like this...
    Code (csharp):
    1.  
    2.     var MGBulletArray : GameObject[];
    3.     var MGBulletTransform : Transform[];
    4.     var MGBulletCollider : Collider[];
    5.     var MGBulletRigidbody : Rigidbody[];
    6.    
    7.  

    Is that correct or is there a much easier way?
     
    Last edited: Feb 1, 2013
  19. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Wow.
    thanks guys.
    Totally works. Something I never would have thought of. Unity Community?! You've done it again!



    edit.
    One last question regarding Awake(), can we ask things like Screen.width in Awake()?
     
  20. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Hi Guys,
    sorry to spam the board with this, but I am seeing another drop in a related field.


    Upon killing my enemy, a coin is reveiled. This coin, is a trigger. Bullets, as they hit the coin make the coin spin faster (animation fps) and can pop out a reweard of its own. My question is, in OnTriggerEnter() I am asking other for its collider, rigidbody, renderer etc. As such, this may be the cause for the dip in overall FPS I am seeing. So, how can I cache other : Collider? It is something unknown to the script? The trigger does not know what other is, until it shows up!


    ???


    Code (csharp):
    1.  
    2. function OnTriggerEnter(other : Collider)
    3. {
    4.     if(other.tag == "brainBullet")
    5.     {      
    6.         if(other.gameObject.layer == 9)
    7.         {
    8.             bulletStrength = 2;
    9.             audio.clip = adHitMulti;
    10.         }
    11.         if(other.gameObject.layer == 13)
    12.         {  
    13.             bulletStrength = 10;
    14.             audio.clip = adHitSingle;
    15.         }
    16.         if(other.gameObject.layer == 15)
    17.         {      
    18.             bulletStrength = 2;    
    19.             audio.clip = adHitSingle;
    20.         }
    21.         if(other.gameObject.layer == 22)
    22.         {      
    23.             bulletStrength = 10;       
    24.             audio.clip = adHitSingle;
    25.         }
    26.        
    27.         bulletBeating = bulletBeating + (bulletStrength/coinWeight);
    28.         other.collider.isTrigger = true;
    29.         other.renderer.enabled = false;
    30.         other.collider.enabled = false;
    31.         other.rigidbody.Sleep();
    32.        
    33.         setCoinSpin();
    34.        
    35.     }
    36. }
     
    Last edited: Feb 1, 2013
  21. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Like in your example, sometimes it is hard(er) to avoid having to get references to components.

    What you need to figure out is whether or not you can tolerate that performance hit vs. potentially making the code a bit more complex, moving logic around, etc.

    Let's assume you cannot afford the performance hit...

    Just to confirm, the code you posted is on the Coin.

    if so, you could move the Component turning on or off to the bullets in OnTriggerEnter there. Cache those Components in the Awake() script sitting on those bullets. Bullets should know how to behave if they hit a coin or a wall or whatever. Right now the coin is telling the bullet what to do. Bullets could be more self aware.

    Next, you're asking Unity to fetch other.gameObject.layer 4 times. Each of those if statements are having to be evaluated individually. If only one of those condition can be true then instead of 4 ifs ... use if () else if () ... unfortunately if the true condition is the last in the chain then you would still be fetching other.gameObject.layer. So why not cache the layer info and fetch it only once.

    Code (csharp):
    1.  
    2. int _otherLayer = other.gameObject.layer; // caching layer of other object so we only need to get it once.
    3.  
    Next, try to avoid using Tags as it is surprisingly slow and uses strings. Strings feed the Garbage Collection monster which reeks havoc on performance. I use layers instead of Tags.

    If you have to use tags then at least replace the other.tag == "bulletBrain" by declaring a const string bulletBrain = "bulletBrain" and check against that instead. == "bulletBrain" gets GC everytime it is evaluated.

    Lastly, I would suggest you look into and event system (C# Events and Delegates) as at some point you may end up with having objects needing to interact with no way easy to access their stuff or worst contemplating using GameObject.Find in update :)

    Once again, you have to figure out whether or not the performance hit warrants making all these changes.

    P.S. I am still new at all of this and although it may be difficult to find on these forums, there is lots of information about this. You just need to keep digging.
     
    Last edited: Feb 2, 2013
  22. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Ok, @ Stephan B,
    Great advice. However, I am running into one issue. I cant seem to get my bullet, collider, onCollisionEnter... collision : Collision, when a trigger, returns nothing. Can I have a collision see a trigger?



    Also, I have implemented everything, I have taken your advice, it is working. Swapped out tags etc. Cached more items. The youtube link you posted is dead. But thanks, it is appreciated.
     
  23. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I fixed the link to that video.

    As per the Docs: "Note that trigger events are only sent if one of the colliders also has a rigidbody attached." But the Rigidbody can be mark as kinematic if you don't want physics to affect it.

    Make sure the Collider is set to trigger if you are checking for OnTriggerEnter. To check for Collision, the Rigidbody cannot be set to Kinematic. So unless you use physics, use triggers.
     
  24. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Well ok,
    I have a bullet. It has a collider and rigidbody. It hits coin. Coin is just a trigger collider. To help performance I want to have the bulletScript handle turning of bullets various components, but in the bulletScript onCollisionEnter, seems unable to pick up coin.

    The bullet is used against other colliders to create some realistic physics. So I am unsure if kinematic would be useful.

    ??
     
    Last edited: Feb 3, 2013
  25. varunvp

    varunvp

    Joined:
    Jul 11, 2014
    Posts:
    57
    So did you finally cache the references of the bullets in an array? If so, how?
     
  26. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Um, well this is an old post, but i suppose it is still relevant to you. Everything is relative, so no worries.

    There are two ways to cache an item (in this case at least), that I like to use... the method to choose depends on number or size of pool and how vital if at all, the order of the members are.

    method one...
    1. create a manager script, poolManager.cs (in C#).
    2. create a bullet script, bulletUnit.cs
    3. create a List in said manager, "public List<bulletUnit> bullets
    4. in the bulletUnit.cs make sure you have a reference to the manager, public poolManager mng;
    5. add the bullet to the list of the manger at start. In Start(), mng.bullets.Add(this);


    This method will automatically add each bulletUnit to a List called bullets in the poolManager. Now, with in the pool manager you can access each bulletUnit.




    method two is kind of pointless at this point. Try this above method.