Skip to content

FlowAgent Core

Nils Meijer edited this page Mar 22, 2024 · 1 revision

In GetClosestAgents(), the agent refers to the singleton AIManager instance, and requests the closest neighbours in a certain distance. By using a singleton, the agent does not need to keep a reference to every single other agent but instead lets the manager do the heavy lifting. Depending on the amount of agents, this can still get performance-heavy, so there are certainly better ways to handle this but that would require more research.

For debugging and visualizations purposes, the agent also gets assigned a color based on how many agents are near.

    private void GetClosestAgents()
    {
        _neighbourAgents = AIManager.Instance.GetNeighbours(this);
        _neighbourCount = _neighbourAgents.Count;

        float lerpProgress = (float)_neighbourCount / AIManager.Instance.TotalAgentCount * COLOR_MULTIPLIER;
        Color noNeighbourColor = Color.red;
        Color hasNeighbourColor = Color.green;
        Color lerpedColor = Color.Lerp(noNeighbourColor, hasNeighbourColor, lerpProgress);
        if(!CurrentlySelected)
            SetColor(lerpedColor);
    }

For the so-called "flocking" behaviour (like flocks of birds or schools of fish are known to do), 3 different parts are needed to construct a working algorithm.

First of all, alignment makes sure every agent that's qualified as a neighbour look in roughly the same direction.

    private Vector3 CalculateAlignment()
    {
        Vector3 alignmentVec = transform.up;
        if (_neighbourCount == 0) 
            return alignmentVec;
        
        foreach (var neighbour in _neighbourAgents)
        {
            alignmentVec += neighbour.transform.up; 
        }
        alignmentVec /= _neighbourCount;
        
        if(_showAlignment)
            Debug.DrawRay(transform.position, alignmentVec, Color.blue);
        
        return alignmentVec;
    }

Second, cohesion takes care of finding the middle position of the "flock" which makes sure the neighbours tend to stay together.

    private Vector3 CalculateCohesion()
    {
        Vector3 cohesionVec = Vector3.zero;
        if (_neighbourCount == 0)
            return cohesionVec;
        
        foreach (var neighbour in _neighbourAgents)
        {
            cohesionVec += neighbour.transform.position; 
        }
        
        cohesionVec /= _neighbourCount;
        cohesionVec -= transform.position;
        
        if(_showCohesion)
            Debug.DrawLine(transform.position, cohesionVec, Color.green);

        return cohesionVec;
    }

Lastly, there is avoidance. This prevents agents from bunching up too close to each other.

    private Vector3 CalculateAvoidance()
    {
        Vector3 avoidanceVec = Vector3.zero;
        if (_neighbourCount == 0)
            return avoidanceVec;
        
        foreach (var neighbour in _neighbourAgents)
        {
            float distance = Vector3.Distance(transform.position, neighbour.transform.position);
            if (distance >= Properties.AvoidanceRadius)
                continue;

            avoidanceVec -= (neighbour.transform.position - transform.position);
        }
        
        if(_showAvoidance)
            Debug.DrawRay(transform.position, avoidanceVec.normalized, Color.red);

        avoidanceVec /= _neighbourCount;

        return avoidanceVec;
    }

All of these aspects combined construct a flocking behaviour. It is a lot of testing & tweaking parameters to see the effects and what values provide a realistic behaviour.

Clone this wiki locally