SimpleBoids

From Virtual More Wiki

Jump to: navigation, search

Scene: SimpleBoids
Unity package: SimpleBoids
Web player: http://virtualmore.org/unity/simpleboids1/

Contents

Background

The Unify Community Wiki provides a simple Flocking script package. It is evident from the code therein that there are four factors/behaviours that determine the movement of each boid:

  1. center - clumping behaviour towards center of flock
  2. velocity - velocity matching behaviour towards average flock velocity
  3. follow - target following behaviour
  4. randomize - random movements

The center behaviour is basically equivalent to the cohesion behaviour of Craig Reynolds’ boids, whereas the velocity behaviour is similar to Reynold's alignment. However, there is a subtle difference in the latter case: In the Flocking scripts, while the velocity of each boid will be adjusted towards the overall velocity of the group of boids, the orientation will not be adjusted. Also, the Flocking scripts do not incorporate the separation behaviour described by Reynolds.

To try out a modified implementation of the Flocking scripts that more resembles Reynolds' behaviours, download our Unity package SimpleBoids. Our implementation orients the boids with their velocity and also incorporates a separation behaviour.

C# Scripts

Original authors: Unify Community and Benoit Benoit FOULETIER (port to C#)
Modified by: User:Roby

BoidControl.cs

 
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public class BoidControl : MonoBehaviour {
 
	public float minVelocity = 1;
	public float maxVelocity = 8;
	public int flockSize = 20;
	public float centerWeight = 1;
	public float velocityWeight = 1;
	public float separationWeight = 1;
	public float followWeight = 1;
	public float randomizeWeight = 1;
 
	public Boid prefab;
	public Transform target;
 
	internal Vector3 flockCenter;
	internal Vector3 flockVelocity;
 
	public List<Boid> boids = new List<Boid>();
 
	void Start()
	{
		for (int i = 0; i < flockSize; i++)
		{
			Boid boid = Instantiate(prefab, transform.position, transform.rotation) as Boid;
			boid.transform.parent = transform;
			boid.transform.localPosition = new Vector3(
							Random.value * collider.bounds.size.x,
							Random.value * collider.bounds.size.y,
							Random.value * collider.bounds.size.z) - collider.bounds.extents;
			boid.controller = this;
			boids.Add(boid);
		}
	}
 
	void Update()
	{
		Vector3 center = Vector3.zero;
		Vector3 velocity = Vector3.zero;
		foreach (Boid boid in boids)
		{
			center += boid.transform.localPosition;
			velocity += boid.rigidbody.velocity;
		}
		flockCenter = center / flockSize;
		flockVelocity = velocity / flockSize;
		}
}
 

Boid.cs

 
using UnityEngine;
using System.Collections;
//using System.Collections.Generic;
 
public class Boid : MonoBehaviour {
 
	internal BoidControl controller;
 
	IEnumerator Start()
	{
		while (true)
		{
			if (controller)
			{
				Vector3 relativePos = steer() * Time.deltaTime;
//				rigidbody.velocity += relativePos; // This is what the Flocking scripts do
                                rigidbody.velocity = steer() * Time.deltaTime; // Alternative	
//				transform.position += steer() * Time.deltaTime;	// This cause bumpy movements			
				transform.rotation = Quaternion.LookRotation(relativePos);
 
				// enforce minimum and maximum speeds for the boids
				float speed = rigidbody.velocity.magnitude;
				if (speed > controller.maxVelocity)
				{
					rigidbody.velocity = rigidbody.velocity.normalized * controller.maxVelocity;
				}
				else if (speed < controller.minVelocity)
				{
					rigidbody.velocity = rigidbody.velocity.normalized * controller.minVelocity;
				}
			}
			float waitTime = Random.Range(0.3f, 0.5f);
			yield return new WaitForSeconds(waitTime);
		}
	}
 
	Vector3 steer()
	{
		Vector3 center = controller.flockCenter - transform.localPosition;			// cohesion
		Vector3 velocity = controller.flockVelocity - rigidbody.velocity; 			// alignment
		Vector3 follow = controller.target.localPosition - transform.localPosition; // follow leader
		Vector3 separation = Vector3.zero; 											// separation
		foreach (Boid boid in controller.boids)
		{
			if (boid == this) {
 
			}
			else {
				Vector3 relativePos= transform.localPosition-boid.transform.localPosition;
				separation += relativePos/(relativePos.sqrMagnitude);				
			}
		}
 
		// 3D space		
		Vector3 randomize = new Vector3(
			(Random.value * 2) - 1, (Random.value * 2) - 1, (Random.value * 2) - 1);// randomize
 
		// 2D space
//		Vector3 randomize = new Vector3((Random.value * 2) - 1, 0, (Random.value * 2) - 1);		
 
		randomize.Normalize();
 
		return (controller.centerWeight*center + 
				controller.velocityWeight*velocity + 
				controller.separationWeight*separation + 
				controller.followWeight*follow + 
				controller.randomizeWeight*randomize);
	}	
}
 

BoidWatch.cs

 
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public class BoidWatch : MonoBehaviour {
 
	public BoidControl boidController;
 
	void LateUpdate()
	{
		if (boidController)
		{
			transform.LookAt(boidController.flockCenter + boidController.transform.position);
		}
	}
}
 

MoveInCircle.cs

This script can also be found here: MoveInCircle.

 
using UnityEngine;
using System.Collections;
 
public class MoveInCircle : MonoBehaviour {
 
	// Move target around circle with tangential speed 
	public float tangentialSpeed;	// m/s
	public float circumference; 	// m
	public float targetRadius; 		// m
	public float period; 			// s
	public float angularSpeed; 		// rad/s
	public float currentAngle; 		// rad/s
 
	// Use this for initialization
	void Start () {
		tangentialSpeed = 6f;	
		circumference = 600f;
		targetRadius = circumference/(2*Mathf.PI);
		period = circumference/tangentialSpeed;
		angularSpeed = 2*Mathf.PI/period;
		currentAngle = 0f;
	}
 
	// Update is called once per frame
	void Update () {
 
		transform.localPosition = targetRadius*(
			new Vector3(Mathf.Sin(currentAngle), 0, Mathf.Cos(currentAngle)));
 
//		rigidbody.velocity = new Vector3(Mathf.Cos(currentDeg), 0, -Mathf.Sin(currentDeg));
 
		currentAngle += angularSpeed*Time.deltaTime;
 
		if (currentAngle > 2*Mathf.PI) {
			currentAngle = currentAngle-2*Mathf.PI;
		}
	}
}
 

Explanation

BoidControl

The BoidControl script gets attached to a game object with a spherical collider. This script allow you to set some boid parameters, including:

  • minVelocity - the minimum velocity of each boid
  • maxVelocity - the maximum velocity of each boid
  • flockSize - the number of boids in your flock
  • Weighting factors for the various behaviours of each boid:
    • centerWeight - equivalent to Reynolds' cohesion behaviour
    • velocityWeight - equivalent to Reynolds' alignment behaviour
    • separationWeight - equivalent to Reynolds' separation behaviour
    • followWeight - equivalent to Reynolds' follow leader behaviour
    • randomizeWeight - addition of random noise
  • prefab - a boid prefab
  • target - a target (leader) that the boids may follow

During the Start() function, the boids are created on random positions on the spherical collider surrounding the BoidControl object. Each boid is added to a list.

At every frame (see Update()), the BoidControl calculates the average position (center) and average velocity of the flock. This is computationally much less intensive than having each boid to this calculation.

Unfortunately, for calculating the separation steering, each boid needs to do this itself. This is because, in order to keep with Reynolds, separation is a repulsive force that is computed by subtracting the positions of a boid and a nearby boid, normalizing (dividing by the distance r), and then applying a 1 / r weighting. That is, the position offset vector is scaled by 1 / r2. Reynolds notes that the 1 / r weighting is just a setting that has worked well, not a fundamental value. These repulsive forces for each nearby boids are summed together to produce the overall steering force.

Boid

Each boid has a steer() function that calculates a cumulative velocity vector based on the five behaviours represented by five variables: 1. center (cohesion) - subtract the boid's position from the flock center to get a vector towards the center of the flock. 2. velocity (alignment) - subtract the boid's velocity from the flock's average velocity to get a vector towards this average. 3. follow (follow leader) - subtract the boid's position from the target (leader) to get a vector towards the leader. 4. separation - for each boid nearby the current boid, subtract the boid's position from the current boid's position to get a vector that points away (repulsive force) from the boid. Normalise this vector by dividing by the magnitude of the vector and then scale it by the same magnitude. Overall, this is equivalent to normalising the vector by 1 / r2. 5. randomize - create a random vector where each component (x, y, z) is in the range of -1 to 1, then normalise it.

The five components are then scaled by the weight given in the BoidControl and added together to produce a steering velocity vector. This velocity vector is multiplied by Time.deltaTime inside a coroutine called Start(). Various ways of moving the boids have been tried. The one that is uncommented seems to work well, and simply adds the steering vector to the boid's velocity. An if-else statement ensures that the boids stay within their velocity range given in BoidControl.

To have the forward direction of each boid aligned with the velocity, the Quaternion.LookRotation is used.

BoidWatch

This script attaches to the camera and makes it look at the flock centre. Because the flock centre variable is in local space (not world space) and relative to the BoidControl, the position of the BoidControl is added.

MoveInCircle

This script attaches to a target/leader and makes it follow a circle with a chosen tangential speed and circumference.

Screenshots

Scene View
Game View

Demo

A web player demo can be found here.

Notes

  • It is much less computationally demanding to use Vector3.sqrMagnitude than Vector3.Magnitude twice when doing the 1 / r2 operation in the steer() function in Boid.cs.
Personal tools