Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Thousands of RigidBodies in Scene - performance improvement

Discussion in 'General Discussion' started by SukhvinderSingh, Jun 9, 2018.

  1. SukhvinderSingh

    SukhvinderSingh

    Joined:
    Jul 18, 2016
    Posts:
    55
    I am making a game in which I need to instantiate a large number of RigidBodies with a Collider component in it.
    Once they are randomly instantiated in the scene, a script throws them towards the player.
    Then, they enter a trigger and sent back to their originating point and they loop forever (Object pooling is used to improve the performance little bit)

    Code for moving rigidbodies is :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. public class ObjectMove : MonoBehaviour {
    5.     public float speed;
    6.     float dir = -1;
    7.     Rigidbody Ast;
    8.     void OnEnable(){
    9.         Ast = transform.GetComponent<Rigidbody> ();
    10.         Ast.velocity = transform.forward * speed *dir ;
    11.     }
    12. }
    Now the problem is, with that large number of rigidbodies, there is a huge drop in performance.
    What can be the possible ways to move objects towards the player, and also, since I am using object pooling, those objects collide with a trigger collider, to be detected and sent back to their origin and loop forever.

    How can the performance of physics be improved?
     
  2. SukhvinderSingh

    SukhvinderSingh

    Joined:
    Jul 18, 2016
    Posts:
    55
    Whatever I do, even make invisible objects disabled, doesn't just help much. In the profiler, FixedUpdate.FixedPhysicsUpdate has the highest percentage.
     
  3. ShilohGames

    ShilohGames

    Joined:
    Mar 24, 2014
    Posts:
    3,015
    What I like to do is completely avoid using a GameObject and Rigidbody on each object. Just create one game object and attach a single script to that game object. Have the script maintain information about where the objects would be using an array of a custom struct. Then update that data each frame during the Update function in that script. In addition to that, use the DrawMeshInstanced method in the Update function to render all of the objects.

    This is how I handled all of the thousands of laser blasts flying around in my space game, Disputed Space. This delivered massively more performance than using a separate game object and rigidbody for each projectile in the scene.
     
    angrypenguin and Kiwasi like this.
  4. SukhvinderSingh

    SukhvinderSingh

    Joined:
    Jul 18, 2016
    Posts:
    55
    Thanks for the reply. But I have done somewhat like yours, I made a single game object with a script that instantiates prefabs on screen and adds velocity to their rigidbodies.
    Once, they enter a trigger, they are deactivated and sent back to the pool. After that, again they are activated at their starting point and the loop continues. This method is called Object pooling. Please have a look at the code:


    Script to make a pool of objects one time:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public class PooledObject {
    7.  
    8.     [HideInInspector]
    9.     public List<GameObject> PoolOfObjects;
    10.     public GameObject[] ListOfObjects;
    11.  
    12.     public int NumberOfObjectsToPool;
    13.     int LengthOfObjects,RandomNumber;
    14.  
    15.     public void Pool() {
    16.         PoolOfObjects = new List<GameObject> ();
    17.  
    18.         LengthOfObjects = ListOfObjects.Length;
    19.  
    20.         for(int i=0;i<NumberOfObjectsToPool;i++) {
    21.  
    22.             RandomNumber = Random.Range (0, LengthOfObjects);
    23.             GameObject obj = (GameObject)MonoBehaviour.Instantiate (ListOfObjects[RandomNumber]);
    24.             obj.SetActive (false);
    25.             PoolOfObjects.Add (obj);
    26.         }
    27.     }
    28.  
    29.     public GameObject RetrievePooledObject() {
    30.         for(int i=0;i<PoolOfObjects.Count;i++) {
    31.             if(!PoolOfObjects[i].activeInHierarchy) {
    32.                
    33.                 return PoolOfObjects[i];
    34.             }
    35.         }
    36.         return null;
    37.     }
    38.  
    39. }
    40.  
    41. public class ObjectPoolingUtility : MonoBehaviour {
    42.  
    43.     public static ObjectPoolingUtility SharedObject;
    44.  
    45.     public PooledObject Asteroid, Star, Heart;
    46.  
    47.     void Awake() {
    48.         ObjectPoolingUtility.SharedObject = this;
    49.     }
    50.  
    51.     void Start () {
    52.  
    53.         Asteroid.Pool ();
    54.         Star.Pool ();
    55.         Heart.Pool ();
    56.        
    57.     }
    58.  
    59. }
    60.  

    Script to activate and deactivate the pooled game-objects and loop forever( I am not using traditional instantiate and destroy) :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class ObjectSpawn : MonoBehaviour {
    6.  
    7.  
    8.     public float Frequency;
    9.     public float Speed;
    10.     float TimeSet;
    11.     float xrange;
    12.     float yrange;
    13.     float zrange;
    14.  
    15.     float thisX;
    16.     float thisY;
    17.     float thisZ;
    18.  
    19.     public float HorizOffset;
    20.     public float VertOffset;
    21.     public float zOffsetMin;
    22.     public float zOffsetMax;
    23.  
    24.     public bool Stars;
    25.     public bool Hearts;
    26.     public bool Asteroids;
    27.  
    28.     Vector3 screenPos;
    29.     GameObject Asteroid;
    30.     GameObject Star;
    31.     GameObject Heart;
    32.  
    33.     void Start () {
    34.         TimeSet = Frequency;
    35.     }
    36.  
    37.     void SpawnRandom() {
    38.  
    39.         thisX = transform.position.x;
    40.         thisY = transform.position.y;
    41.         thisZ = transform.position.z;
    42.        
    43.         xrange = Random.Range (-(thisX+HorizOffset), thisX+HorizOffset);
    44.         yrange = Random.Range (-(thisY+VertOffset),thisY+VertOffset);
    45.         zrange = Random.Range (thisZ+zOffsetMin,thisZ+zOffsetMax);
    46.         screenPos = new Vector3(xrange,yrange,zrange);
    47.  
    48.  
    49.         if(Asteroids==true) {
    50.             Asteroid = ObjectPoolingUtility.SharedObject.Asteroid.RetrievePooledObject ();
    51.             if(Asteroid!=null) {
    52.                 Asteroid.transform.position = screenPos;
    53.                 Asteroid.transform.rotation = Quaternion.identity;
    54.                 Asteroid.GetComponent<Rigidbody>().velocity=-transform.forward*Speed;
    55.                 Asteroid.SetActive (true);
    56.             }
    57.         }
    58.  
    59.         else if(Stars==true) {
    60.             Star = ObjectPoolingUtility.SharedObject.Star.RetrievePooledObject ();
    61.             if(Star!=null) {
    62.                 Star.transform.position = screenPos;
    63.                 Star.transform.rotation = Quaternion.identity;
    64.                 Star.GetComponent<Rigidbody>().velocity=-transform.forward*Speed;
    65.  
    66.                 Star.SetActive (true);
    67.             }
    68.        
    69.         }
    70.  
    71.         else if(Hearts==true) {
    72.             Heart = ObjectPoolingUtility.SharedObject.Heart.RetrievePooledObject ();
    73.             if(Heart!=null) {
    74.                 Heart.transform.position = screenPos;
    75.                 Heart.transform.rotation = Quaternion.identity;
    76.                 Heart.GetComponent<Rigidbody>().velocity=-transform.forward*Speed;
    77.  
    78.                 Heart.SetActive (true);
    79.             }
    80.  
    81.         }
    82.     }
    83.  
    84.     void TimedInstantiate() {
    85.         Frequency -= Time.deltaTime;
    86.         if (Frequency <= 0) {
    87.             SpawnRandom ();
    88.             Frequency = TimeSet;
    89.         }
    90.     }
    91.  
    92.     void Update () {
    93.         TimedInstantiate ();
    94.     }
    95. }
    96.  
    This is what I did.

    Now the problem is scripts are well optimized according to the profiler, but the physics takes a huge toll on CPU. Any idea how to manage such a huge number of rigidbodies. Also, I cannot ignore rigidbodies because I need to detect collisions.
     
  5. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    789
    Your problem sounds like an absolute prime candidate for the new ECS (Entity component system).

    https://forum.unity.com/forums/entity-component-system-and-c-job-system.147/

    There are quite a few topics and even videos about it.
    That should massively improve performance for you if done right.

    In my experience you can easily get a ~20x performance improvement, in some cases even 100x...
    I'm using ECS for many small systems where I have tons of little objects. (So little that a classic GameObject would be overkill). I have systems to shoot many small missiles for spells in my game, also smoke effects, little "health bars" that need raycasting...

    You have a similar situation I guess.

    As for detecting collisions, maybe you can do sphere/circle checks instead (which are just some subtraction, not even requiring a square root if you compare DistanceSquared())?

    Or you can make your own little spatial hashing algorithm? (They can be pretty simple if you basically map the position of some object to an array by simply rounding to the closest integer, or multiple of 10 or something).
     
    SukhvinderSingh likes this.
  6. SukhvinderSingh

    SukhvinderSingh

    Joined:
    Jul 18, 2016
    Posts:
    55
    Thanks!
    Will have a look at it.
     
  7. ShilohGames

    ShilohGames

    Joined:
    Mar 24, 2014
    Posts:
    3,015
    No, what you are describing is completely different than what I am talking about. My method is what I call an Instancing Pool. I have done object pools before, and an instancing pool style solution delivers about 6-10 times as much performance as an object pool when dealing with thousands of items. With an instancing pool, you don't instantiate any game objects from prefabs. You just keep track of where those objects would have been, compute what they would have done, and then render them using DrawMeshInstanced instead of using all of those game objects.
     
    angrypenguin likes this.
  8. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Your core problem is thousands of rigidbodies. It's a very easy mistake to make, assuming physics will perform well there. It won't. Which is why most games actually go to physics as a last resort.

    Your scenario looks fairly simple. I think you would get huge gains by simply moving transforms directly without physics, and raycasting from the projectile to the target every frame. That's without any of the more advanced stuff like instanced rendering or jobs/ECS.

    I would do that and pay more attention to simple stuff like not calling GetComponent so often. Also in your object pooling don't activate/deactivate gameobjects, enable/disable renderers. Use a custom class for your pooled object so you can set references to the transform and renderers when you create the pool. That will make a difference.

    From there the next steps going into more advanced stuff would be instanced rendering and/or moving your raycasting to use RaycastCommand, which does the raycasting off the main thread.
     
    Kiwasi likes this.
  9. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    This isn't the same .

    To clarify why, this still creates thousands of objects which have to be individually executed. The array approach puts most of the work into one data structure in one object, which is far more efficient. As a simplified explanation, the way that CPUs and memory interact means that having code execution switch between objects is often far more expensive than the calculations done within any one object.

    To use a flawed metaphor, it's like having a bunch of dominoes scattered around a room that you need to knock over, vs. having a row of dominoes in one place that can all be knocked over at once. Either way, knocking down a dominoe is really fast, but in one of those cases you'll spend most of your time moving around the room.


    Separately, this should be in the Scripting section.
     
    Last edited: Jun 11, 2018
    Kiwasi likes this.
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    First question. Why do you need a thousand RigidBodies?

    A RigidBody gets involved in a full Newtonian simulation. Most games only ever have a small number of objects that actually need full Newtonian simulation. Even physics heavy games like Kerball top out at a couple of hundred.

    Executing only the small part of physics that you actually need is much more effective. From the sounds of it, you could simply Lerp a Transform, then reset its position. No physics needed.
     
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    Indeed, an object moving from one known position to another does not need a physics simulation. Furthermore, if there's only one object it can collide with and you know the specifics of that collision in advance you can have radically simplified collision detection.

    When I did something like this I then copied the position data over into a particle system to get efficient drawing of objects en-masse, but we've now got DrawMeshInstanced(...) which likely gives both more flexibility and improved performance.
     
    Kiwasi likes this.