Large numbers AI agents. Optimization

Hello everybody. I am trying to make a simulator with a large numbers (ideally over +3000) of indipendent agents. Every agent has a rigidbody and a collider attached. The rigidbody is needed for movement using AddForce and the collider for collision with enemies. They also have material with friction = 0 to smooth movement. I am able to run it a +30 fps with over 700 agents, but I am aware that there is (and I will need it) a lot of room for improvement, in particular when there will be enemies, a more complex graphics (which is now very simple for prototyping purposes). The code for each agent is:

using UnityEngine;
using System.Collections;

public class civilian3D : MonoBehaviour {

Vector2 randomDirection;
public float speed = 2f;
Rigidbody rigidbody;

void Awake () {
rigidbody = gameObject.GetComponent();

}

void FixedUpdate()
{
randomDirection = Random.insideUnitCircle * 5;
rigidbody.AddForce(new Vector3(randomDirection.x, randomDirection.y, 0f));
Rotation();
}

void Rotation()
{
Vector2 moveDirection;
moveDirection = gameObject.GetComponent().velocity;
if (moveDirection != Vector2.zero)
{
float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
Quaternion anglezombie = Quaternion.Euler(new Vector3(0, 0, angle));
transform.rotation = anglezombie;
}
}
}


Which is the best way to optimize it? Remove rigidbodies/colliders, alter this code or make calculation simpler? I also tried InvokeRepeating instead of Update but it seems to make the game slower.
I am also thinking to move to 2D.

Any help is appreciated!

Firstly please use code tags to display code.

Looking at your code, specifically the Rotation method. Why are you geting the rigidbody component again? -You have a reference that you got in you Awake function. Just use that reference. That will stop the mass getcomponent calls every fixed update.

Secondly, if you are using a rigidbody you really shouldnt be touching the transform directly like you do in Rotation. Use rigidbody.MoveRotation instead. It will keep the physics engine happy.

Finally, this seems to be AI code right? Does it really need to be invoked every fixed update? Thats many times per second. Could you try it inside a coroutine and set that to update every 1 second or half a second? Play around until you get an update speed that is a good balance between gameplay and performance.

Thanks a lot for the reply. Sorry for the game tags, I was used to Unity answers where there is a direct icon. But I managed to find it also here.
Thank for the advices, in particular the first one was my mistake.
I will implement corountines as you suggest. Before I use InvokeRepeating but it seems slowering things.
I will post soon an update and thank you very much

Following advices gives me better results. Should I switch to kinematic rigidbody for better performance?

If you have 3000 agents, rigidbodies are a bad idea.

what would you suggest?

Translate.

Use the Profiler to see what slows your game down. Depending on your target platform, and how close each agent is to each other and obstacles (and your physics layer maps), 3000 agents with Rigidbodies are definitely doable.

1 Like

Don’t know if that would work, just a quick shot from the hip:

using UnityEngine;
using System.Collections;

public class Civilian : MonoBehavior {
 
  public float speed;
  public float turn;
  public float updateInterval;
 
  private Vector3 randomDir;
  private Transform tf;
  private Rigidbody rb;
  private float dT;
 
  void Awake () {
 
  rb = GetComponent<Rigidbody>();
  tf = GetComponent<Transform>();
  }
 
  void Start () {
 
  InvokeRepeating ( "updateCivilian", updateInterval )
  }
 
  private void updateCivilian () {
 
  randomDir = Random.insideUnitCircle;
  }
 
  void Update () {
 
  dT = Time.deltaTime;
 
  // Rotation
  tf.rotation = Quaternion.RotateTowards (
  tf.rotation,
  Quaternion.LookRotation ( randomDir ),
  dT * turn
  );
 
  // Movement
  tf.Translate ( Vector3.forward * speed * dT );
  }
}

This is moving the Transform, I guess moving the Rigidbody would be better.

Thanks everybody for all hints. I run the profiler, and as I imagined physics calculations are putting the cpu on the knees:2249229--150264--profiler.JPG

The big peak occurs after I spawned 1400 “cube agents” samples:2249229--150265--game view.JPG

By the way, for simplicity and to test it, I just throw away the rotation function, so basically there is only a corountine which calls every 3 sec the random movement function adding force.
Any hints about this situation? I do not know if switching to 2d would be better, or doing something else. Actually the rigidbody is needed to detect collision between the agents and enemies and buildings. Maybe I should find a way to avoid collision detection only between “friends” agents?

Try and replace the box collider with a sphere collider.

Update: removing v-synch and changing Fixed timestep greatly improve performance.
I also switched to 2D agent and now I am able to have +2500 agents at 20fps.

I think what you could do to improve this much more is have group AI. Say one ai agent manages 3-4 units at one time. These units always move together in a similar direction. Could work?

I agree with Korno, however you can go a step further, create a manager class which caches and updates each instance. Also let the manager class call FixedUpdate and then manually call the fixed update (with a different name) in your agent classes, this use to be alot quicker but may have changed with updates. Cache as many variables as possible and try not to create any “new” instances after the start. I would also ditch the physics movement and just calculate the new position and rotation in the update for each agent. If your still slow try processing less each frame e.g. process 1/2 the agents each frame but calculate their new positions twice the distance. This reduces the rotation calculations to half and to compensate the jerky effect this will create by lerping the movement.

Update: changed layer collision matrix to reduce collision: now civilians do not detect collisions among themselves, resulting in big improvement.

Thank you Korno for the idea. Yes, I think it could work. I will try to figure out how to set up random group size (let’s say 1 to 5 agents) at the beginning of the scene and let them follow “the leader”.

Also, thanks bajeo88 for all your tips. But what do you mean for a manager class to cache the agents? Do you mean add them to some list at the beginning of the scene when they spawn, and cycle through them?

Now the code for every agent is:

using UnityEngine;
using System.Collections;

public class civilian3D : MonoBehaviour {

    Vector2 randomDirection;
    public float speed = 2f;
    Rigidbody rigidbody;

    // Use this for initialization
    void Awake () {
        rigidbody = gameObject.GetComponent<Rigidbody>();
        StartCoroutine("randomMovement");

    }


    IEnumerator randomMovement()
    {
        while(true){
            randomDirection = Random.insideUnitCircle * 5;
            rigidbody.AddForce(new Vector3(randomDirection.x, randomDirection.y, 0f));
            yield return new WaitForSeconds(2f);
        }
    }
}

So, you are back to 3D?

You could restrict the rigidbody movement on the z axis and do

rigidbody.AddForce(Random.insideUnitCircle * 5);

Plus you could disable collision detect with the ground.

You can cache that WaitForSeconds too. You dont need yield a new one everytime.

In as surprising twist, (for me at least), nav mesh agents performed better in my project then physics based characters.

Something to consider, set up a simple stress test and see what happens.

I am still testing both 2d and 3d for the moment. I followed your advice for collision detection with ground and restricted movement.

Done :slight_smile:

By the way, thanks everybody. Now things are getting better, with +3000 agents (2d or 3d) running at 25fps on a tile map of 70x70 procedurally generated. If I restrict the view, I can also get 50/60!
2251095--150424--Cattura.JPG
2251095--150424--Cattura.JPG

Pro tip: Use the profiler and measure things in ms rather then in frames per second. It’s more consistent and reliable.