I have a flocking script for some ducks based on a youtube video I watched. The script works really well and I added my own basic obstacle avoidance system. However, performance is shocking! On average, every bird seems to take away 1 fps. That doesn't sound too bad but it scales really poorly. I have a 4km x 4km map, and I want a lot of birds. With no birds, I have about 130-140fps. With 100 birds, that drops to 40-50fps. I would really like to be able to increase the number of birds to at least 200 and still have around 100fps. I know my code isn't very well optimized but I just don't how to do that. Below is are my two flocking scripts. I know for a fact that it is these scripts because the profiler has the flock.update() at about 20-30%. Each bird also has a ragdoll system so the player can shoot them and they will fall out of the sky. This means that each bird has a rigidbody and collider on each joint. I've also added an LOD system so the birds are culled outside a certain distance. Could someone please take a quick look at them and see if I'm doing something particularly expensive that could be done better?
GlobalFlock.cs
public class GlobalFlock : MonoBehaviour
{
[SerializeField]
private GameObject birdPrefab;
[SerializeField]
private Transform parent;
[SerializeField]
private int tankSize = 200;
private float yOffset = 10;
private static int numBirds = 100;
public static GameObject[] allBirds = new GameObject[numBirds];
void Start()
{
for (int i = 0; i < numBirds; i++)
{
//creating a random position within a 'tank' to spawn each bird
Vector3 pos = new Vector3(Random.Range(-tankSize, tankSize), Random.Range(yOffset, yOffset + 1), Random.Range(-tankSize, tankSize));
//makes the tanks position reletive to this transform
pos += transform.position;
//instantiating birds and parenting them to an empty to reduce clutter in the heirachy
allBirds[i] = (GameObject)Instantiate(birdPrefab, pos, Quaternion.Euler(0, Random.Range(-360, 360), 0));
allBirds[i].transform.parent = parent;
}
}
}
Flock.cs
public class Flock : MonoBehaviour
{
private float speed;
private float startSpeed;
private float rotationSpeed = 4.0f;
private Vector3 averageHeading;
private Vector3 averagePosition;
private float neighbourDistance = 4.0f;
private bool flee;
private Vector3 fleePos;
private float fleeDistance = 100.0f;
private float fleeRotationSpeed = 6.0f;
private Transform[] rayTransforms;
private int layer = 16;
private int layerMask;
private Vector3 goalPos;
private Vector3 childPos;
[SerializeField]
private float rayLength;
[HideInInspector]
public bool ragdoll;
private States states;
void Start()
{
speed = Random.Range(4, 6);
startSpeed = speed;
states = FindObjectOfType<States>();
rayTransforms = transform.GetChild(1).GetComponentsInChildren<Transform>();
childPos = transform.GetChild(1).position;
layerMask = ~(1 << layer);
}
void Update()
{
if (!ragdoll)
{
//randomly changing the goal position 5 times out of 10000
if (Random.Range(0, 10000) < 3)
{
goalPos = new Vector3(Random.Range(-2000, 2000),
Random.Range(0, 200),
Random.Range(-2000, 2000));
}
//scare the birds - flee
if (states.shoot)
{
fleePos = states.transform.GetChild(0).position;
if (Vector3.Distance(fleePos, this.transform.position) < fleeDistance)
{
flee = true;
}
}
else
{
if (Vector3.Distance(fleePos, this.transform.position) >= fleeDistance)
{
flee = false;
}
}
if (Random.Range(0, 5) < 1)
ApplyRules();
transform.Translate(0, 0, Time.deltaTime * speed);
}
}
void ApplyRules()
{
GameObject[] gos;
gos = GlobalFlock.allBirds;
Vector3 vCenter = Vector3.zero;
Vector3 vAvoid = Vector3.zero;
float gSpeed = 0.1f;
float dist = 0;
int groupSize = 0;
foreach (GameObject go in gos)
{
if (go != this.gameObject && go)
{
dist = Vector3.Distance(go.transform.position, this.transform.position);
if (dist <= neighbourDistance)
{
vCenter += go.transform.position;
groupSize++;
if (dist < 1.0f)
{
vAvoid += this.transform.position - go.transform.position;
}
Flock anotherFlock = go.GetComponent<Flock>();
gSpeed += anotherFlock.speed;
}
}
}
Ray lRay = new Ray(childPos, rayTransforms[1].forward * rayLength);
Ray rRay = new Ray(childPos, rayTransforms[2].forward * rayLength);
Ray uRay = new Ray(childPos, rayTransforms[3].forward * rayLength);
Ray dRay = new Ray(childPos, rayTransforms[4].forward * rayLength);
if (Physics.Raycast(dRay, rayLength, layerMask))
{
transform.localEulerAngles += new Vector3(-1, 0, 0);
}
else if (Physics.Raycast(uRay, rayLength, layerMask))
{
transform.localEulerAngles += new Vector3(1, 0, 0);
}
else if (Physics.Raycast(lRay, rayLength, layerMask))
{
transform.localEulerAngles += new Vector3(0, 1, 0);
}
else if (Physics.Raycast(rRay, rayLength, layerMask))
{
transform.localEulerAngles += new Vector3(0, -1, 0);
}
if (flee)
{
Vector3 newFleePos = new Vector3(fleePos.x, transform.position.y, fleePos.z);
Vector3 direction = transform.position - newFleePos;
speed = Mathf.Lerp(speed, 9.0f, Time.deltaTime * 2);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * fleeRotationSpeed);
}
else
{
if (groupSize > 0)
{
vCenter = vCenter / groupSize + (goalPos - this.transform.position);
speed = (speed == startSpeed) ? (gSpeed / groupSize) : Mathf.Lerp(speed, startSpeed, Time.deltaTime);
Vector3 direction = (vCenter + vAvoid) - transform.position;
if (direction != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * rotationSpeed);
}
}
else
{
speed = Mathf.Lerp(speed, startSpeed, Time.deltaTime);
Vector3 direction = goalPos - this.transform.position;
if (direction != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * rotationSpeed);
}
}
}
}
}