florisvdaa

Floris van der Aa

Floris transparent logo

Flocking system

How does it work?

“Flocking” refers to the collective behavior observed in groups of animals, particularly birds, where individuals within the group move in a coordinated an cohesive manner. Flocking is a natural phenomenon that has been extensively studied by biologists, ecologists, and computer scientists. The study of flocking behavior has applications in various fields, including biology, ecology, computer science, and robotics. Biologists study flocks to understand how animals coordinate their movements for purpose such as migration, predator evasion, and foraging. In computer science, flocking behavior is used as an inspiration for creating algorithms and simulations for tasks like crowd simulation, traffic modeling, and swarm robotics. One of the most famous computational models of flocking behavior is Craig Reynold’s “Boids” algorithm, which simulates the flocking behavior of birds. This algorithm uses simple rules for cohesion, alignment, and separation to create complex and realistic flocking simulations.

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

public class Flock : MonoBehaviour
{
    public FlockAgent agentPrefab;
    List<FlockAgent> agents = new List<FlockAgent>(); // stores flocks in a list
    public FlockBehavior behavior; // here we can put in the scriptable object

    [Range(10, 500)]
    public int startingCount = 250;
    const float agentDesity = 0.08f; // this way we can populate it randomly in a circle, we can also change the size of the circle by the number of flocks in the circle

    [Range(1f, 100f)] //1f is default speed
    public float driveFactor = 10f;
    [Range(1f, 100f)]
    public float maxSpeed = 5f; // max speed, changeble with slider
    [Range(1f, 10f)]
    public float neighborRadius = 1.5f; // radius for the avoidance
    [Range(0f, 1f)]
    public float avoidanceRadiusMultiplier = 0.5f;


    float squareMaxSpeed;
    float squareNeighborRadius;
    float squareAvoidanceRadius;

    public float SquareAvoidanceRadius { get { return squareAvoidanceRadius; } }
    void Start()
    {
        squareMaxSpeed = maxSpeed * maxSpeed; // calculates the squareroot
        squareNeighborRadius = neighborRadius * neighborRadius; // calculates the squareroot
        squareAvoidanceRadius = squareNeighborRadius * avoidanceRadiusMultiplier * avoidanceRadiusMultiplier;

        for (int i = 0; i < startingCount; i++)
        {
            FlockAgent newAgent = Instantiate(
                agentPrefab, 
                Random.insideUnitCircle * startingCount * agentDesity, // the size of the circle is based on the number of agents we have in the Flock
                Quaternion.Euler(Vector3.forward * Random.Range(0,360)), // makes the Agent rotate towards a random point
                transform
                );

            newAgent.name = "Agent" + i;
            newAgent.Initialize(this);
            agents.Add(newAgent);
        }
    }

    void Update()
    {
        foreach (FlockAgent agent in agents)
        {
            List<Transform> context = GetNearbyObjects(agent);

            //agent.GetComponentInChildren<SpriteRenderer>().color = Color.Lerp(Color.white, Color.red, context.Count / 6f); // Demo

            Vector2 move = behavior.calculateMove(agent, context, this);
            move *= driveFactor;
            if (move.sqrMagnitude > squareMaxSpeed)
            {
                move = move.normalized * maxSpeed; // reset the speed so it is back to 1,
            }
            agent.Move(move);
        }        
    }

    List<Transform> GetNearbyObjects(FlockAgent agent) // returns a list of collisions
    {
        List<Transform> context = new List<Transform>();
        Collider2D[] contextColliders = Physics2D.OverlapCircleAll(agent.transform.position, neighborRadius);
        foreach (Collider2D c in contextColliders) // iterates over all colliders found in the list we just made
        {
            if (c != agent.AgentCollider) // checks if it is not our own collider
            {
                context.Add(c.transform);
            }

        }
        return context;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Collider2D))]
public class FlockAgent : MonoBehaviour
{
    Flock agentFlock;
    public Flock AgentFlock { get { return agentFlock; } }

    Collider2D agentCollider;
    public Collider2D AgentCollider { get { return agentCollider; } } // now we have acces to it, so we only need to assign it once in the start method.
    void Start()
    {
        agentCollider = GetComponent<Collider2D>(); // it finds whatever instance is atteched to this object
    }

    public void Initialize(Flock flock)
    {
        agentFlock = flock;
    }
    
    public void Move(Vector2 velocity) // it get's the offset position of where it is going to
    {
        transform.up = velocity; // makes the sprite face up and always face towards the direction it is going
        transform.position += (Vector3)velocity * Time.deltaTime; // adds the velocity to the position, this way it will move towards it. I also cast the 'velocity' to a vector 3 otherwise you can not add them becouse transform.position is a V3
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "Flock/Behavior/Avoidance")]
public class AvoidanceBehavior : FilteredFlockBehavior
{
    public override Vector2 calculateMove(FlockAgent flockAgent, List<Transform> context, Flock flock)
    {
        //if no neighbors, return no adjustment
        if (context.Count == 0)
        {
            return Vector2.zero;
        }

        // add all points together and average
        Vector2 avoidanceMove = Vector2.zero;

        int nAvoid = 0;

        List<Transform> filterContext = (filter == null) ? context : filter.Filter(flockAgent, context);

        foreach (Transform item in filterContext)
        {
            if (Vector2.SqrMagnitude(item.position - flockAgent.transform.position) < flock.SquareAvoidanceRadius)
            {
                nAvoid++;
                avoidanceMove += (Vector2)(flockAgent.transform.position - item.position); // cast to V2
            }
        }

        if (nAvoid > 0)
        {
            avoidanceMove /= nAvoid;
        }
        return avoidanceMove;

        // this part calculates the middle part between the neighbors en moves there
    }
}