Boid and birds in Unity3D

Have you ever thought about why the birds flying in large flocks and never faced? In 1986 a man named Craig Reynolds, who decided to create a simple model of behavior of birds in flocks, and called it Boids. In the model, each Boyd has three basic rules: Separation, Alignment and Cohesion. The first is to avoid collision with the neighbors, the second makes to fly around in the same direction as the neighbors, and the third says not to fly alone and keep the group. These simple rules allow you to create believable flocks of birds, fish and other animals, which is used by film and gaming industry.

In this article I will discuss how to implement this model in practice. I use to develop Unity and C #, but most things are true for the other engines and languages.

Create a new project in Unity and immediately create some folders for the future: Materials, Prefabs, Scenes, Scripts.
Throws on stage Directional Light and one area named Boid. Sphere becomes a prefab. At the same time save the scene immediately, then to not think about it. We now proceed to scripting.

For the model, we need to calculate the three parameters: Separation, Alignment and Cohesion. Let's start with the latter, it is the easiest. Let me remind you, this is a vector directed towards the center of the surrounding Boyd. To find it necessary to lay down the coordinates of Boyd and divide the sum by the number. As Boyd discovers that he has the neighbors? For this useful Physics.OverlapSphere. This function will return us all colliders in a given cohesionRadius, including our Boyd if he enters the sphere.

boids = Physics.OverlapSphere(transform.position, cohesionRadius);

Zeroing variable, add, divide, and then draw a line from the center painted transformer Boyd via superpoleznyh Debug.DrawLine and Color.magenta. Debug.DrawLine takes the input coordinates of the beginning and end of the line, as well as optional color line. The results of all functions debazhnyh visible only during development, build, they just do not get.

Debug.DrawLine(transform.position, cohesion, Color.magenta);

Boid.cs

using UnityEngine;

public class Boid : MonoBehaviour
{
    private Vector3 cohesion;
    private float cohesionRadius = 10;
    private Collider[] boids;

    void Update()
    {
        cohesion = Vector3.zero;
        boids = Physics.OverlapSphere(transform.position, cohesionRadius);
        foreach (var boid in boids)
        {
            cohesion += boid.transform.position;
        }
        cohesion = cohesion / boids.Length;
        Debug.DrawLine(transform.position, cohesion, Color.magenta);
    }
}

We throw the script on Prefab, copy Boyd couple of times and hit Play. Do not forget to turn on the Gizmos otherwise not see the line.

Boyd collect in a bunch

  Now we need to convert the resulting point set in motion. Keep everything in one pile of bad, so it has been the previous code into a separate function. Function will start the timer using InvokeRepeating. The first argument - the name of the function, the second - the start time, and the third - a repeat interval. This feature is very useful for the delayed start of different scenarios.

InvokeRepeating("CalculateVelocity", 0, 1);

To calculate the vector we use school math and subtract from the coordinates of the center coordinates of the Boyd. Add a script public (I say then why) variable velocity, at the beginning of zero out its functions, and at the end priplyusuem her new vector cohesion. In the Update will make the result to the coordinates transformer taking into account the past tense. Time.deltaTime need to move was not dependent on FPS and going at the same speed on all processors.

transform.position += velocity * Time.deltaTime;

In addition, since our center has become a vector, then we will change our Debug.DrawLine on the other no less fantastic Debug.DrawRay. No difference, just the second argument must be in relative coordinates, just like us.

Boid.cs cohesion

using UnityEngine;

public class Boid : MonoBehaviour
{
    public Vector3 velocity;

    private float cohesionRadius = 10;
    private Collider[] boids;
    private Vector3 cohesion;

    private void Start()
    {
        InvokeRepeating("CalculateVelocity", 0, 1);
    }

    void CalculateVelocity()
    {
        velocity = Vector3.zero;
        cohesion = Vector3.zero;
        boids = Physics.OverlapSphere(transform.position, cohesionRadius);
        foreach (var boid in boids)
        {
            cohesion += boid.transform.position;
        }
        cohesion = cohesion / boids.Length;
        cohesion = cohesion - transform.position;
        velocity += cohesion;
    }

    void Update()
    {
        transform.position += velocity * Time.deltaTime;
        Debug.DrawRay(transform.position, cohesion, Color.magenta);
    }
}

Divide Boyd

Calculation of separation a little harder. You need to calculate the most useful way to overcome the heap neighbors. To this can be found by weighted sum of vectors each neighbor. Vector from a neighbor divide by the distance to it, which is produced by Vector3.magnitude. In the resulting sum of the largest weight will have close neighbors.

separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude;

It makes sense to limit the number of neighbors taken into account a certain distance, it will add a variable to the counter and one for the radius of separation.

if ((transform.position - boid.transform.position).magnitude < separationDistance)

Furthermore, we do not need hit zero vector in the amount due to the collider itself Boyd. Do not forget that Physics.OverlapSphere covers all colliders, including Collider Boyd. Therefore, little changed conditions.

if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance)

Boid.cs separation

using UnityEngine;

public class Boid : MonoBehaviour
{
    public Vector3 velocity;

    private float cohesionRadius = 10;
    private float separationDistance = 5;
    private Collider[] boids;
    private Vector3 cohesion;
    private Vector3 separation;
    private int separationCount;

    private void Start()
    {
        InvokeRepeating("CalculateVelocity", 0, 1);
    }

    void CalculateVelocity()
    {
        velocity = Vector3.zero;
        cohesion = Vector3.zero;
        separation = Vector3.zero;
        separationCount = 0;

        boids = Physics.OverlapSphere(transform.position, cohesionRadius);
        foreach (var boid in boids)
        {
            cohesion += boid.transform.position;

            if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance)
            {
                separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude;
                separationCount++;
            }
        }

        cohesion = cohesion / boids.Length;
        cohesion = cohesion - transform.position;
        if (separationCount > 0)
        {
            separation = separation / separationCount;
        }

        velocity += cohesion + separation;
    }

    void Update()
    {
        transform.position += velocity * Time.deltaTime;

        Debug.DrawRay(transform.position, separation, Color.green);
        Debug.DrawRay(transform.position, cohesion, Color.magenta);
    }
}

Organize Boyd


To Boyd not just mindlessly going straight to a handful of us need them to repeat the behavior of neighbors. Calculation of alignment is very simple, we summarize the public variables velocity (aha!) From each neighbor and divide by the number of them. Access to hitch scripts can be accessed using GameObject.GetComponent. He can find not only scripts, but in general, any components. Wonderful thing.

alignment += boid.GetComponent<Boid>().velocity;

Boid.cs alignment

using UnityEngine;

public class Boid : MonoBehaviour
{
    public Vector3 velocity;

    private float cohesionRadius = 10;
    private float separationDistance = 5;
    private Collider[] boids;
    private Vector3 cohesion;
    private Vector3 separation;
    private int separationCount;
    private Vector3 alignment;

    private void Start()
    {
        InvokeRepeating("CalculateVelocity", 0, 1);
    }

    void CalculateVelocity()
    {
        velocity = Vector3.zero;
        cohesion = Vector3.zero;
        separation = Vector3.zero;
        separationCount = 0;
        alignment = Vector3.zero;

        boids = Physics.OverlapSphere(transform.position, cohesionRadius);
        foreach (var boid in boids)
        {
            cohesion += boid.transform.position;
            alignment += boid.GetComponent<Boid>().velocity;

            if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance)
            {
                separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude;
                separationCount++;
            }
        }

        cohesion = cohesion / boids.Length;
        cohesion = cohesion - transform.position;
        if (separationCount > 0)
        {
            separation = separation / separationCount;
        }
        alignment = alignment / boids.Length;

        velocity += cohesion + separation + alignment;
    }

    void Update()
    {
        transform.position += velocity * Time.deltaTime;

        Debug.DrawRay(transform.position, separation, Color.green);
        Debug.DrawRay(transform.position, cohesion, Color.magenta);
        Debug.DrawRay(transform.position, alignment, Color.blue);
    }
}

Run ... and zero responses, all the same. Add 2 in the formula for calculating velocity.

velocity += cohesion + separation + alignment*2;

Cut vectors


  We have increased the vector alignment, which increased the velocity vector, which increased vector alignment that ... Well, you understand. We need to make the maximum speed limit. Moreover, the restriction is necessary to put more and all the components of the vector, or in some cases the behavior of Boyd gets a little strange. Can try.

For circumcision vectors in Unity is a function Vector3.ClampMagnitude. Just add after calculating each of the vectors construction of the following form:

velocity = Vector3.ClampMagnitude(velocity, maxSpeed);

Boid.cs clamp

using UnityEngine;

public class Boid : MonoBehaviour
{
    public Vector3 velocity;

    private float cohesionRadius = 10;
    private float separationDistance = 5;
    private Collider[] boids;
    private Vector3 cohesion;
    private Vector3 separation;
    private int separationCount;
    private Vector3 alignment;
    private float maxSpeed = 15;

    private void Start()
    {
        InvokeRepeating("CalculateVelocity", 0, 1f);
    }

    void CalculateVelocity()
    {
        velocity = Vector3.zero;
        cohesion = Vector3.zero;
        separation = Vector3.zero;
        separationCount = 0;
        alignment = Vector3.zero;

        boids = Physics.OverlapSphere(transform.position, cohesionRadius);
        foreach (var boid in boids)
        {
            cohesion += boid.transform.position;
            alignment += boid.GetComponent<Boid>().velocity;

            if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance)
            {
                separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude;
                separationCount++;
            }
        }

        cohesion = cohesion / boids.Length;
        cohesion = cohesion - transform.position;
        cohesion = Vector3.ClampMagnitude(cohesion, maxSpeed);
        if (separationCount > 0)
        {
            separation = separation / separationCount;
            separation = Vector3.ClampMagnitude(separation, maxSpeed);
        }
        alignment = alignment / boids.Length;
        alignment = Vector3.ClampMagnitude(alignment, maxSpeed);

        velocity += cohesion + separation * 10 + alignment * 1.5f;
        velocity = Vector3.ClampMagnitude(velocity, maxSpeed);
    }

    void Update()
    {
        if (transform.position.magnitude > 25)
        {
            velocity += -transform.position.normalized;
        }

        transform.position += velocity * Time.deltaTime;

        Debug.DrawRay(transform.position, separation, Color.green);
        Debug.DrawRay(transform.position, cohesion, Color.magenta);
        Debug.DrawRay(transform.position, alignment, Color.blue);
    }
}

Check the operation of vectors.

Automating


  For a software arrangement Boyd exists a function Instantiate. At the entrance it is necessary to submit a reference to the object to be copied, the new coordinates of the object and its rotation. For copied prefab we make a separate public variable which will fill in the inspector. Random location convenient to take out Random.insideUnitSphere, simply multiply it by the required radius of the sphere. Our Boyd can be rotated as much as necessary, the result will be, so use Quaternion.identity, which means no rotation.

Instantiate(boidPrefab, Random.insideUnitSphere * 25, Quaternion.identity);

In a series of repetitive actions above and obtain any desired amount of Boyd. Throw new script on an empty object in the center of the stage and fill the link to prefab.

HeartOfTheSwarm.cs

using UnityEngine;

public class HeartOfTheSwarm : MonoBehaviour
{
    public Transform boidPrefab;
    public int swarmCount = 100;


    void Start()
    {
        for (var i = 0; i < swarmCount; i++)
        {
            Instantiate(boidPrefab, Random.insideUnitSphere * 25, Quaternion.identity);
        }
    }
}

The rapidly flying away a bunch of Boyd's not very convenient to watch a good idea to put them on the chain. To do this, add a small condition Update:

if (transform.position.magnitude > 25)
{
    velocity += -transform.position.normalized;
}

With it, Boyd, whose coordinates are abroad virtual sphere will rotate towards the center. Finally a little play with multipliers vectors, and other parameters, otherwise you get the desired effect. See the final code under the spoiler below.

velocity += cohesion + separation * 10 + alignment * 1.5f;

Final code

using UnityEngine;

public class Boid : MonoBehaviour
{
    public Vector3 velocity;

    private float cohesionRadius = 10;
    private float separationDistance = 5;
    private Collider[] boids;
    private Vector3 cohesion;
    private Vector3 separation;
    private int separationCount;
    private Vector3 alignment;
    private float maxSpeed = 15;

    private void Start()
    {
        InvokeRepeating("CalculateVelocity", 0, 0.1f);
    }

    void CalculateVelocity()
    {
        velocity = Vector3.zero;
        cohesion = Vector3.zero;
        separation = Vector3.zero;
        separationCount = 0;
        alignment = Vector3.zero;

        boids = Physics.OverlapSphere(transform.position, cohesionRadius);
        foreach (var boid in boids)
        {
            cohesion += boid.transform.position;
            alignment += boid.GetComponent<Boid>().velocity;

            if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance)
            {
                separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude;
                separationCount++;
            }
        }

        cohesion = cohesion / boids.Length;
        cohesion = cohesion - transform.position;
        cohesion = Vector3.ClampMagnitude(cohesion, maxSpeed);
        if (separationCount > 0)
        {
            separation = separation / separationCount;
            separation = Vector3.ClampMagnitude(separation, maxSpeed);
        }
        alignment = alignment / boids.Length;
        alignment = Vector3.ClampMagnitude(alignment, maxSpeed);

        velocity += cohesion + separation * 10 + alignment * 1.5f;
        velocity = Vector3.ClampMagnitude(velocity, maxSpeed);
    }

    void Update()
    {
        if (transform.position.magnitude > 25)
        {
            velocity += -transform.position.normalized;
        }

        transform.position += velocity * Time.deltaTime;

        Debug.DrawRay(transform.position, separation, Color.green);
        Debug.DrawRay(transform.position, cohesion, Color.magenta);
        Debug.DrawRay(transform.position, alignment, Color.blue);
    }
}

The code listed below is outdated, the latest version, see the Procedural Toolkit

GitHub | Unity Web Player

If anyone interested how I did the animation.

using UnityEngine;

public class Screenshot : MonoBehaviour
{
    private int count;

    void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            InvokeRepeating("Capture", 0.1f, 0.3f);
        }
    }

    void Capture()
    {
        Application.CaptureScreenshot(Application.dataPath + "/Screenshot" + count + ".png");
        count++;
    }
}

 

 

All data posted on the site represents accessible information that can be browsed and downloaded for free from the web.

 

http://habrahabr.ru/post/182382/

 

User replies

No replies yet