Flocking using Unity's samples

In this section, we'll create our own scene with flocks of objects and implement the flocking behavior in C#. There are two main components in this example: the individual boid behavior and a main controller to maintain and lead the crowd.

Our scene hierarchy is shown in the following screenshot:

Flocking using Unity's samples

The scene hierarchy

As you can see, we have several boid entities, UnityFlock, under a controller named UnityFlockController. The UnityFlock entities are individual boid objects and they'll reference to their parent UnityFlockController entity to use it as a leader. The UnityFlockController entity will update the next destination point randomly once it reaches the current destination point.

The UnityFlock prefab is a prefab with just a cube mesh and a UnityFlock script. We can use any other mesh representation for this prefab to represent something more interesting, like birds.

Mimicking individual behavior

Boid is a term, coined by Craig Reynold, that refers to a bird-like object. We'll use this term to describe each individual object in our flock. Now let's implement our boid behavior. You can find the following script in UnityFlock.cs, and this is the behavior that controls each boid in our flock.

The code in the UnityFlock.cs file is as follows:

using UnityEngine;
using System.Collections;

public class UnityFlock : MonoBehaviour {
  public float minSpeed = 20.0f;
  public float turnSpeed = 20.0f;
  public float randomFreq = 20.0f;
  public float randomForce = 20.0f;

  //alignment variables
  public float toOriginForce = 50.0f; 
  public float toOriginRange = 100.0f;

  public float gravity = 2.0f;

  //seperation variables
  public float avoidanceRadius = 50.0f;
  public float avoidanceForce = 20.0f;

  //cohesion variables
  public float followVelocity = 4.0f;
  public float followRadius = 40.0f;

  //these variables control the movement of the boid
  private Transform origin;
  private Vector3 velocity;
  private Vector3 normalizedVelocity;
  private Vector3 randomPush;
  private Vector3 originPush;
  private Transform[] objects;
  private UnityFlock[] otherFlocks;
  private Transform transformComponent;

We declare the input values for our algorithm that can be set up and customized from the editor. First, we define the minimum movement speed, minSpeed, and rotation speed, turnSpeed, for our boid. The randomFreq value is used to determine how many times we want to update the randomPush value based on the randomForce value. This force creates a randomly increased and decreased velocity and makes the flock movement look more realistic.

The toOriginRange value specifies how spread out we want our flock to be. We also use toOriginForce to keep the boids in range and maintain a distance with the flock's origin. Basically, these are the properties to deal with the alignment rule of our flocking algorithm. The avoidanceRadius and avoidanceForce properties are used to maintain a minimum distance between individual boids. These are the properties that apply the separation rule to our flock.

The followRadius and followVelocity values are used to keep a minimum distance with the leader or the origin of the flock. They are used to comply with the cohesion rule of the flocking algorithm.

The origin object will be the parent object to control the whole group of flocking objects. Our boid needs to know about the other boids in the flock. So, we use the objects and otherFlocks properties to store the neighboring boids' information.

The following is the initialization method for our boid:

  void Start () {
    randomFreq = 1.0f / randomFreq;

    //Assign the parent as origin
    origin = transform.parent;

    //Flock transform
    transformComponent = transform;

    //Temporary components
    Component[] tempFlocks= null;

    //Get all the unity flock components from the parent 
    //transform in the group
    if (transform.parent) {
      tempFlocks = transform.parent.GetComponentsInChildren
          <UnityFlock>();
    }

    //Assign and store all the flock objects in this group
    objects = new Transform[tempFlocks.Length];
    otherFlocks = new UnityFlock[tempFlocks.Length];

    for (int i = 0;i<tempFlocks.Length;i++) {
      objects[i] = tempFlocks[i].transform;
      otherFlocks[i] = (UnityFlock)tempFlocks[i];
    }

    //Null Parent as the flock leader will be 
    //UnityFlockController object
    transform.parent = null;

    //Calculate random push depends on the random frequency 
//provided
    StartCoroutine(UpdateRandom());
  }

We set the parent of the object of our boid as origin; it means that this will be the controller object to follow generally. Then, we grab all the other boids in the group and store them in our own variables for later references.

The StartCoroutine method starts the UpdateRandom() method as a co-routine:

  IEnumerator UpdateRandom() {
    while (true) {
      randomPush = Random.insideUnitSphere * randomForce;
      yield return new WaitForSeconds(randomFreq + 
          Random.Range(-randomFreq / 2.0f, randomFreq / 2.0f));
    }
  }

The UpdateRandom() method updates the randomPush value throughout the game with an interval based on randomFreq. The Random.insideUnitSphere part returns a Vector3 object with random x, y, and z values within a sphere with a radius of the randomForce value. Then, we wait for a certain random amount of time before resuming the while(true) loop to update the randomPush value again.

Now, here's our boid behavior's Update() method that helps our boid entity comply with the three rules of the flocking algorithm:

  void Update () {
    //Internal variables
    float speed = velocity.magnitude;
    Vector3 avgVelocity = Vector3.zero;
    Vector3 avgPosition = Vector3.zero;
    float count = 0;
    float f = 0.0f;
    float d = 0.0f;
    Vector3 myPosition = transformComponent.position;
    Vector3 forceV;
    Vector3 toAvg;
    Vector3 wantedVel;

    for (int i = 0;i<objects.Length;i++){
      Transform transform= objects[i];
      if (transform != transformComponent) {
        Vector3 otherPosition = transform.position;

        // Average position to calculate cohesion
        avgPosition += otherPosition;
        count++;

        //Directional vector from other flock to this flock
        forceV = myPosition - otherPosition;

        //Magnitude of that directional vector(Length)
        d= forceV.magnitude;

        //Add push value if the magnitude, the length of the 
        //vector, is less than followRadius to the leader
        if (d < followRadius) {
          //calculate the velocity, the speed of the object, based
           //on the avoidance distance between flocks if the 
          //current magnitude is less than the specified 
          //avoidance radius
          if (d < avoidanceRadius) {
            f = 1.0f - (d / avoidanceRadius);
            if (d > 0) avgVelocity += 
                (forceV / d) * f * avoidanceForce;
          }

          //just keep the current distance with the leader
          f = d / followRadius;
          UnityFlock tempOtherFlock = otherFlocks[i];
          //we normalize the tempOtherFlock velocity vector to get 
          //the direction of movement, then we set a new velocity
          avgVelocity += tempOtherFlock.normalizedVelocity * f * 
              followVelocity;  
        }
      }
    }

The preceding code implements the separation rule. First, we check the distance between the current boid and the other boids and update the velocity accordingly, as explained in the comments.

Next, we calculate the average velocity of the flock by dividing the current velocity with the number of boids in the flock:

    if (count > 0) {
      //Calculate the average flock velocity(Alignment)
      avgVelocity /= count;

      //Calculate Center value of the flock(Cohesion)
      toAvg = (avgPosition / count) - myPosition;
    }  
    else {
      toAvg = Vector3.zero;
    }

    //Directional Vector to the leader
    forceV = origin.position -  myPosition;
    d = forceV.magnitude;   
    f = d / toOriginRange;

    //Calculate the velocity of the flock to the leader
    if (d > 0) //if this void is not at the center of the flock
        originPush = (forceV / d) * f * toOriginForce;

    if (speed < minSpeed && speed > 0) {
      velocity = (velocity / speed) * minSpeed;
    }

    wantedVel = velocity;

    //Calculate final velocity
    wantedVel -= wantedVel *  Time.deltaTime;
    wantedVel += randomPush * Time.deltaTime;
    wantedVel += originPush * Time.deltaTime;
    wantedVel += avgVelocity * Time.deltaTime;
    wantedVel += toAvg.normalized * gravity * Time.deltaTime;

    //Final Velocity to rotate the flock into
    velocity = Vector3.RotateTowards(velocity, wantedVel, 
        turnSpeed * Time.deltaTime, 100.00f);

    transformComponent.rotation = Quaternion.LookRotation(velocity);

    //Move the flock based on the calculated velocity
    transformComponent.Translate(velocity * Time.deltaTime, 
        Space.World);

    //normalise the velocity
    normalizedVelocity = velocity.normalized;
  }
}

Finally, we add up all the factors such as randomPush, originPush, and avgVelocity to calculate our final target velocity, wantedVel. We also update our current velocity to wantedVel with linear interpolation using the Vector3.RotateTowards method. Then, we move our boid based on the new velocity using the Translate() method.

Next, we create a cube mesh and add this UnityFlock script to it, and make it a prefab, as shown in the following screenshot:

Mimicking individual behavior

The Unity flock prefab

Creating the controller

Now it is time to create the controller class. This class updates its own position so that the other individual boid objects know where to go. This object is referenced in the origin variable in the preceding UnityFlock script.

The code in the UnityFlockController.cs file is as follows:

using UnityEngine;
using System.Collections;

public class UnityFlockController : MonoBehaviour {
  public Vector3 offset;
  public Vector3 bound;
  public float speed = 100.0f;

  private Vector3 initialPosition;
  private Vector3 nextMovementPoint;

  // Use this for initialization
  void Start () {
    initialPosition = transform.position;
    CalculateNextMovementPoint();
  }

  // Update is called once per frame
  void Update () {
    transform.Translate(Vector3.forward * speed * Time.deltaTime);
    transform.rotation = Quaternion.Slerp(transform.rotation, 
        Quaternion.LookRotation(nextMovementPoint - 
        transform.position), 1.0f * Time.deltaTime);

    if (Vector3.Distance(nextMovementPoint, 
        transform.position) <= 10.0f) 
        CalculateNextMovementPoint();
  }

In our Update() method, we check whether our controller object is near the target destination point. If it is, we update our nextMovementPoint variable again with the CalculateNextMovementPoint() method we just discussed:

  void CalculateNextMovementPoint () {
    float posX = Random.Range(initialPosition.x - bound.x, 
        initialPosition.x + bound.x);
    float posY = Random.Range(initialPosition.y - bound.y, 
        initialPosition.y + bound.y);
    float posZ = Random.Range(initialPosition.z - bound.z, 
        initialPosition.z + bound.z);

    nextMovementPoint = initialPosition + new Vector3(posX, 
        posY, posZ);
  }
}

The CalculateNextMovementPoint() method finds the next random destination position in a range between the current position and the boundary vectors.

Putting it all together, as shown in the previous scene hierarchy screenshot, you should have flocks flying around somewhat realistically:

Creating the controller

Flocking using the Unity seagull sample

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset