top of page

Flocking Algorithm In Unity

Updated: Sep 4, 2021



To flock means to group together. Flocking is the behavior exhibited when a group of birds(or any other species), called a flock, are foraging or in flight. This behavior which can be seen in many living creatures on earth is interesting and super easy to simulate in Unity using C#. Take a look at the demo of the flocking functionality I coded and read on to know how !


helped me in putting together my algorithm. This algorithm is a generic one, it is easy to understand and to convert into C# using the Unity Game Engine.


I used two scripts to simulate flocking behaviour - a script called “World.cs” which runs on a controller/manager game object and a script called “Agent.cs” which runs on every member of the flock called an agent. From the demo video, each individual bird can be called an agent.


What does World.cs do ?

Well, since it runs on a manager game object, it holds information about all the agents of the flock in addition to spawning the agents of the flock in the world. It defines and uses the following two functions.

"SpawnAgents()" - takes the number of agents as an input to spawn them in the world.

"GetNeighbors()" - takes an agent and a radius as input and outputs all the neighbors of that agent in the specified radius.


Take a look at the implementation details of the aforementioned functions.

private void SpawnFlockingAgents()
    {
        for (int i = 0; i < numberOfAgentsToSpawn; i++)
        {
            FlockingAgent agent = Instantiate(flockingAgent, new Vector3(Random.Range(-10f, 10f), Random.Range(-10f, 10f), Random.Range(-10f, 10f)), Quaternion.identity);
            worldFlockingAgents.Add(agent);
        }
    }

public List<FlockingAgent> GetNeighbors(FlockingAgent agent, float radius) 
=> worldFlockingAgents.Where(t => (Vector3.Distance(t.GetPosition(), agent.GetPosition()) < radius) && t != agent).ToList();

What does Agent.cs do ?

Before getting into the working of the Agent.cs script, let us first understand what concepts put together will make flocking happen. Our flocking algorithm can be divided into 3 parts:

Cohesion - Cohesion means sticking together. In a flock agents stick together which moves all the agents towards the center of the flock.

Separation - Cohesion brings agents towards the center, while separation moves agents away from their neighbors to avoid colliding into them.

Alignment - Alignment makes sure all the agents face the same direction while flocking.

The three rules of flocking should make the understanding of Agent.cs script easier. The script defines and uses the following three functions.


Our main flocking logic will go in to the "Agent.cs" script, which is attached to every agent of the flock.

Our flocking algorithm can be divided into 3 parts:

  1. Cohension() -> Cohension() - The neighbors of an agent are dynamically calculated using the GetNeighbors() function in the World.cs script. Once the neighbors of an agent are determined, the centre of the flock is calculated using the neighbors’ positions. This center point of the flock can be used to calculate the direction the agent should move, to stay within the flock. This is called every frame, for every agent in the world. Every agent tries to move towards the center of the flock and by doing so, it stays with the flock.

 private Vector3 Cohension()
    {
        if(!agentWorld.agentSettings.activateCohension)
            return Vector3.zero;

        //Calculate the neighbors of agents
        List<FlockingAgent> neighbors = agentWorld.GetNeighbors(this, agentWorld.agentSettings.cohensionRadius);

        if (neighbors.Count == 0)
            return Vector3.zero;
            
       //Calculate the center of all neighbor agents.
        Vector3 sumOfNeightborPositions = Vector3.zero;
        foreach (var item in neighbours)
        {
            sumOfNeightborPositions += item.GetPosition();
        }

        Vector3 center = sumOfNeightborPositions / neighbors.Count;

        //Once we get center, we calculate vector towards center
        return (center - GetPosition()).normalized;
    }


2. Separation() -> Cohension brings agent towards center, while separation moves agents away from their neighbors. In separation, a vector away from each of the neighbor agents - Away vector is calculated, and the sum of all away facing vectors to get a resultant separation vector is determined. Separation vector makes sure agents don't stick to each other.


ree

As shown in the above picture, green vectors are the individual separation vectors and the red vector is the resultant separation vector from all its blue neighbor agents.


This is a code snippet to calculate the resultant separation vector for each agent. The code is well commented which explains the steps mentioned above.

    private Vector3 Separation()
    {
        if (!agentWorld.agentSettings.activateSeparation)
            return Vector3.zero;

        Vector3 separationVector = new Vector3();

        //Calculate neighbors of an agent
        List<FlockingAgent> neighbors = agentWorld.GetNeighbors(this, agentWorld.agentSettings.separationRadius);

        if (neighbors.Count == 0)
            return Vector3.zero;

        foreach (var item in neighbors)
        {
            //Individual seperation vector from a neighbor agent
            Vector3 direction = this.GetPosition() - item.GetPosition();

            if(direction.magnitude > 0)
            {
                //Calculating resultant separation Vector.
                separationVector += (direction.normalized) / (direction.magnitude);
            }
        }

        return separationVector.normalized;
    }
 

3. Alignment() -> Alignment makes sure all the agents face the same direction while flocking. The code snippet for alignment is pretty simple and self explanatory for alignment.

   private Vector3 Alignment()
    {
        if (!agentWorld.agentSettings.activateAlignment)
            return Vector3.zero;

        Vector3 alignment = new Vector3();

        List<FlockingAgent> neighbors = agentWorld.GetNeighbors(this, agentWorld.agentSettings.alignmentRadius);

        if (neighbors.Count == 0)
            return Vector3.zero;

        foreach (var item in neighbors)
        {
            alignment += item.agentVelocity;
        }

        return alignment.normalized;
    }

Now we have 3 Vectors - one from Cohention, one from Separation and one from Alignment. The sum of these 3 vectors gives us the Resultant vector which an agent uses to move in a flock. Each agent follows these 3 rules and calculates the resultant vector to move in a flock while making sure to stick within the flock.


There is one last vector to calculate which is optional, if you wish to move the flock towards a target position.


    public Vector3 FaceTargetposition()
    {
        if (!agentWorld.agentSettings.activateTagetReachedDesire)
            return Vector3.zero;

        return (agentWorld.targetPosition.position - this.GetPosition()).normalized;
    }

Note now there are 4 vectors to sum to calculate the resultant Vector.


Here is the complete agent.cs code, which have to be attached to every agent in the flock.


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlockingAgent : MonoBehaviour
{
    private World agentWorld;
    private Vector3 agentVelocity = Vector3.zero;
    private Vector3 agentAcceleration;

    #region Unity Methods
    private void Awake()
    {
        agentWorld = FindObjectOfType<World>();

        agentVelocity = new Vector3(Random.Range(0.1f, -0.1f), Random.Range(0.1f, -0.1f), Random.Range(0.1f, -0.1f));
    }


    private void Update()
    {
        agentAcceleration = (agentWorld.agentSettings.alignmentFactor * Alignment())
                          + (agentWorld.agentSettings.cohensionFactor * Cohension())
                          + (agentWorld.agentSettings.separationFactor * Separation())
                          + (agentWorld.agentSettings.targetReachFactor * FaceTargetposition());

        agentAcceleration = Vector3.ClampMagnitude(agentAcceleration, agentWorld.agentSettings.maxAcceleration);
       
        agentVelocity += agentAcceleration * Time.deltaTime;
        agentVelocity = Vector3.ClampMagnitude(agentVelocity, agentWorld.agentSettings.maxVelocity);

        transform.position += agentVelocity * Time.deltaTime;

        transform.forward = agentVelocity;
    }

    #endregion

    #region Flocking Calculations
    private Vector3 Cohension()
    {
        if(!agentWorld.agentSettings.activateCohension)
            return Vector3.zero;

        //Calculate the center of agents
        List<FlockingAgent> neighbors = agentWorld.GetNeighbors(this, agentWorld.agentSettings.cohentionRadius);

        if (neighbors.Count == 0)
            return Vector3.zero;

        //Averaging positions of all neighbor agents to calculate center
        Vector3 sumOfNeightborPositions = Vector3.zero;
        foreach (var item in neighbors)
        {
            sumOfNeightbourPositions += item.GetPosition();
        }

        Vector3 center = sumOfNeightborPositions / neighbors.Count;

        return (center - GetPosition()).normalized;
    }

    private Vector3 Separation()
    {
        if (!agentWorld.agentSettings.activateSeparation)
            return Vector3.zero;

        Vector3 separationVector = new Vector3();

        //Calculate neighbors of an agent
        List<FlockingAgent> neighbors = agentWorld.GetNeighbors(this, agentWorld.agentSettings.seperationRadius);

        if (neighbors.Count == 0)
            return Vector3.zero;

        foreach (var item in neighbors)
        {
            //Individual seperation vector from a neighbor agent
            Vector3 direction = this.GetPosition() - item.GetPosition();

            if(direction.magnitude > 0)
            {
                //Calculating resultant separation Vector.
                separationVector += (direction.normalized) / (direction.magnitude);
            }
        }

        return separationVector.normalized;
    }

    private Vector3 Alignment()
    {
        if (!agentWorld.agentSettings.activateAlignment)
            return Vector3.zero;

        Vector3 alignment = new Vector3();

        List<FlockingAgent> neighbors = agentWorld.GetNeighbors(this, agentWorld.agentSettings.alignmentRadius);

        if (neighbors.Count == 0)
            return Vector3.zero;

        foreach (var item in neighbors)
        {
            alignment += item.agentVelocity;
        }

        return alignment.normalized;
    }

    public Vector3 FaceTargetposition()
    {
        if (!agentWorld.agentSettings.activateTagetReachedDesire)
            return Vector3.zero;

        return (agentWorld.targetPosition.position - this.GetPosition()).normalized;
    }

    #endregion


    #region Agent Helper Function
    public Vector3 GetPosition() => transform.position;

    #endregion

}

Download my project and feel free to play around with different settings for flocking in the Config folder and observe the behavior of the agents.

Watch the following video to see how the behavior of the flock changes as I tweak the flock settings.


To see how Cohention, separation and Alignment works, watch this video.




 
 
 

Recent Posts

See All

Comments


bottom of page