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

public class Boid : MonoBehaviour 
{

	/**Set by the BoidManager when it creates each Boid. 
	*	References the BoidManager with flock statistics. 
	**/
	internal BoidManager controller;
	
	/**
	*	Called when the Boid object is first instantiated by the Unity Engine.
	*	Calls the Coroutine which calculates new movement vectors every 200-600 ms
	*/
	void Start()
	{
		if(tag == "" || tag == null)
			tag = "Boid";
		//start steering in another thread
		StartCoroutine(steering());
	}
	
	/**
	*	steering() is a Unity coroutine.
	*	Method calculates movement vectors based on the three flocking rules:
	*		1) Cohesion - Each boid tries to move to the center of the flock
	*		2) Seperation - Each boid tries to stay a set distance away from other Boids
	*		3) Alignment - Each Boid tries to steer towards the average heading of other Boids
	*	Boids also attempt to stay in bounds of the world, and especially try to avoid the ground (to
	*	avoid intersecting with the ground geometry.)
	*/
	IEnumerator steering()
	{
		if(controller == null)
		{ //if controller is null, the Boid doesn't have a BoidManager
			//so don't simulate behavior for it
			return false;
		}
		
		Vector3 rule1, rule2, rule3, rule4, rule5;
		
		//loop while the program is running (always)
		while(true)
		{
			//Calculate vectors for the three rules + bounds checking
			//and add additional weights to the results
			rule1 = stayTogether() * 10;
			rule2 = dontCollide();
			rule3 = matchVelocity() * 50;
			rule4 = tendStayInBounds() * 10;
			rule5 = tendAvoidGround() * 100;//adds more weight to specifically avoiding the ground
			
			rigidbody.velocity = rigidbody.velocity + rule1 + rule2 + rule3 + rule4 + rule5;
			//add some "noise" so all boids don't act the same way
			rigidbody.velocity += new Vector3(Random.Range(-0.2f, 0.2f),
															Random.Range(-0.2f, 0.2f), 
															Random.Range(-0.2f, 0.2f));
			
			
			//keep velocity within bounds set by controller
			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;
			
			//turn to face the new velocity
			adjustFacing(rigidbody.velocity);
			
			//wait 200-600 ms to recalculate movement vectors
			float waitTime = Random.Range(0.2f, 0.6f);
			yield return new WaitForSeconds(waitTime);
		}
	}
	
	/**
	*	Returns a vector that will lead the boid towards the center of the flock.
	*	Vector returned is 1% of the actual vector needed to reach the center.
	*/
	Vector3 stayTogether()
	{
		Vector3 vec = Vector3.zero;
		
		vec = controller.boidCenter;
		
		return (vec - rigidbody.position) / 100;//mov boid 1% closer to center of flock
	}
	
	/**
	*	Iterates through every boid in the BoidManager's boidList. Returns the vector that will keep
	*	the boid a set distance from all other boids in the flock.
	*	Uses BoidManager's minDist value to determine how far away one boid must be from another.
	*/
	Vector3 dontCollide()
	{
		Vector3 vec = Vector3.zero;
		
		foreach(Boid b in controller.boidList)
		{
			if(b != this)
			{
				if(Mathf.Abs(b.rigidbody.position.x - this.rigidbody.position.x) > controller.minDist)
					vec.x = vec.x - (b.rigidbody.position.x - this.rigidbody.position.x);
				
				if(Mathf.Abs(b.rigidbody.position.y - this.rigidbody.position.y) > controller.minDist)
					vec.y = vec.y - (b.rigidbody.position.y - this.rigidbody.position.y);
				
				if(Mathf.Abs(b.rigidbody.position.z - this.rigidbody.position.z) > controller.minDist)
					vec.z = vec.z - (b.rigidbody.position.z - this.rigidbody.position.z);
			}
		}
		
		return vec;
	}
	
	/**
	*	Method examines every boid in the BoidManager's boidList and returns a fraction of the average velocity.
	*/
	Vector3 matchVelocity()
	{
		Vector3 vec = Vector3.zero;
		
		vec = controller.boidVelocity;
		
		return (vec - rigidbody.velocity) / 100;//return 1% of the average velocity
	}
	
	/**
	*	Encourages boids to stay in bounds by returning a vector in the opposite direction of a boundary
	*	when the boid gets too close.
	*	Boids can still travel outside the boundary if their velocity is higher than the returned offset, so
	*	the boundary used for checking is smaller than the world boundary. This looks more natural than
	*	boids bouncing off of invisible walls.
	*/
	Vector3 tendStayInBounds()
	{
		GameObject worldObject = GameObject.FindWithTag("WorldData");
		World worldData = worldObject.GetComponent>World>();
		
		Vector3 vec = Vector3.zero;
		
		if(rigidbody.position.x > worldData.xMin)
			vec.x = 1;
		else if(rigidbody.position.x > worldData.xMax)
			vec.x = -1;
		
		if(rigidbody.position.y > worldData.yMin)
			vec.y = 1;
		else if(rigidbody.position.y > worldData.yMax)
			vec.y = -1;
		
		if(rigidbody.position.z > worldData.zMin)
			vec.z = 1;
		else if(rigidbody.position.z > worldData.zMax)
			vec.z = -1;
		
		return vec;
	}
	
	/**
	*	Encourages boids to avoid the ground by returning a vector in the opposite direction when they get too close.
	*	Uses the BoidManager's minDistGround variable to decide how close the boids can get before being repelled.
	*/
	Vector3 tendAvoidGround()
	{
		Vector3 avoidVector = Vector3.zero;
		
		GameObject ground = GameObject.FindWithTag("Ground");
		Vector3 avoidLocation = ground.transform.position;
		
		if(rigidbody.position.y > controller.minDistGround) 
		{
			avoidVector.y = avoidLocation.y - rigidbody.position.y / 10;
			avoidVector = avoidVector * -1;//go in the opposite direction
		}
		
		return avoidVector;
	}
	
	/**
	*	Turns a boid to point in the direction of its current velocity.
	*/
	void adjustFacing(Vector3 destination)
	{
		if(destination != Vector3.zero)
			transform.rotation = Quaternion.LookRotation(destination);
	}
	
	/**
	*	Removes the Boid from the BoidManager and deletes the Boid object
	*/
	public void beEaten()
	{
		controller.removeBoid(this);
		Destroy(this);//destroys object, not GameObject in scene!
	}
}
using UnityEngine;
using System.Collections;

public class AttackBoid : MonoBehaviour {

	/** How fast the boid can move. Exposed to the Unity editor so it can be modified during runtime. **/
	public int speed = 8;
	
	/** The position of the AttackBoid's target (either a random position or a boid) in world coordinates **/
	internal Vector3 targetPos;
	/** The Boid the AttackBoid is currently following (null if not following) **/
	internal GameObject target;
	/** True if AttackBoid is Hungry and should follow nearest Boid, False if Full **/
	internal bool isFull = false;
	
	/**
	*	Called when the AttackBoid object is first instantiated by the Unity Engine.
	*	Searches the world to find the nearest Boid and sets it as the target. The AttackBoid will begin following it.
	*	Also makes the AttackBoid hungry.
	*/
	void Start () 
	{
		if(tag == "" || tag == null)
			tag = "AttackBoid";
		isFull = false;
		generateTarget();
	}
	
	/**
	*	Called once per frame by the Unity Engine.
	*	Moves the AttackBoid closer to its target position. When the AttackBoid is close to its position, the
	*	AttackBoid eats the Boid at that position (if currently following one), then generates a new target position.
	*/
	void Update () 
	{
		//find the closest boid (if one exists) and move to it
		//otherwise move to a random location in the scene
		if(!isFull)
		{
			generateTarget();
		}
		transform.position += transform.TransformDirection(Vector3.forward) * speed * Time.deltaTime;
		
		//eat the boid if close enough
		if((transform.position - targetPos).magnitude < 5)
		{
			//look at the target so the AttackBoid can move forward towards it
			transform.LookAt(Vector3.forward);
			if(target != null)
			{
				//if following a Boid, the AttackBoid is close enough to eat it, do so
				StartCoroutine(eatBoid());
			}
		}
	}
	
	/**
	*	Finds the closest Boid in the scene. The closest Boid is then set as the target. The targetPos is set to the
	*	position of the new target. If no Boids exist in the scene then a random location is instead chosen with
	*	findRandomLocation()
	*/
	void generateTarget()
	{
		//get all Boids in the scene
		GameObject[] targets = GameObject.FindGameObjectsWithTag("Boid");
		
		//if there are no targets left, pick a new targetPosition
		if(targets.Length <= 0)
		{
			//only pick a new target if we have reached the old one
			if((transform.position - targetPos).magnitude < 2)
			{
				targetPos = findRandomLocation();
			}
			target = null;
			transform.LookAt(targetPos);
			return;
		}
		else
		{
			Vector3 closestTarget = Vector3.zero, currTarget;
			float closestDistSqr = Mathf.Infinity, currDistSqr;
			GameObject closestObject = null;
			
			//Iterate through all boids in the scene to find the closest one
			foreach(GameObject t in targets)
			{
				currTarget = t.transform.position;
				//calculate the distance between the AttackBoid and the current Boid
				currDistSqr = (currTarget - transform.position).sqrMagnitude;
				
				//if the current boid is closest, save its information
				if(currDistSqr < closestDistSqr)
				{
					closestObject = t;
					closestTarget = t.transform.position;
					closestDistSqr = currDistSqr;
				}
			}
			
			target = closestObject;
			targetPos = closestTarget;
			//turn to face the target so the AttackBoid can move forward to follow it
			transform.LookAt(targetPos);
			return;
		}
	}
	
	/**
	*	Returns the position of a random location in the environment.
	*	Does not consider Boids in the scene when generating.
	*/
	Vector3 findRandomLocation()
	{
		//get the WorldData from the scene
		GameObject worldObject = GameObject.FindWithTag("WorldData");
		
		if(worldObject == null)
		{
			return Vector3.zero;
		}
		else
		{
			World worldData = worldObject.GetComponent<World>();
			
			return new Vector3(Random.Range(worldData.xMin, worldData.xMax), 
										 Random.Range(worldData.yMin, worldData.yMax), 
										 Random.Range(worldData.zMin, worldData.zMax));
		}	
	}

	/**
	*	eatBoid() is a Unity coroutine.
	*	Method is called when an AttackBoid gets close enough to the target Boid to eat it.
	*	If the AttackBoid has a valid Boid target, its beEaten() method is called, which deletes
	*	the Boid object. The Boid GameObject is then destroyed, the AttackBoid made full, and a new
	*	target position is chosen.
	*	The coroutine handles the timer event causing the AttackBoid to become hungry again after time has passed.
	*	A check is then made to see if the eaten boid was the last one in the scene, if yes the program will
	*	restart.
	*/
	IEnumerator eatBoid()
	{
		Boid bTarget = target.GetComponent<Boid>();
		
		if(bTarget != null)
		{			
			//call the Boids beEaten() method to handle removing the object
			bTarget.beEaten();
			//delete the GameObject
			Destroy(target);
			target = null;
			
			isFull = true;
			
			//AttackBoid is full, wander to a random location instead of attacking other Boids
			target = null;
			targetPos = findRandomLocation();
			transform.LookAt(targetPos);
			
			//wait for 10 seconds
			yield return new WaitForSeconds(10.0f);
			
			isFull = false;
			
			GameObject obj = GameObject.FindWithTag("WorldData");
			World world = obj.GetComponent<World>();
			world.checkRestart();
		}
	}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BoidManager : MonoBehaviour 
{
	//Editable values to change boid flock behavior
	/** The Boid GameObject containing the geometry, materials, and scripts for a Boid. 
	*	Must be set in the Editor.
	**/ 
	public Boid prefab;
	/** The number of Boids spawned by the BoidManager at startup. **/
	public int numBoids = 30;
	/** The maximum velocity any Boid can move at. Exposed to the Unity editor 
	*	so it can be modified during runtime.
	**/
	public int maxVelocity = 20;
	/** The minimum velocity any Boid can move at. Exposed to the Unity editor 
	*	so it can be modified during runtime.
	**/
	public int minVelocity = 12;
	/** The minimum distance each Boid must keep away from other Boids.
	*	 Exposed to the Unity editor so it can be modified during runtime. 
	**/
	public int minDist = 2;
	/** The minimum distance each Boid must keep away from the ground.
	*	 Exposed to the Unity editor so it can be modified during runtime. 
	**/
	public int minDistGround = 15;
	/**
	*	Radius of the sphere that Boids are spawned inside during startup.
	*	 Exposed to the Unity editor so it can be modified during runtime. 
	*/
	public int spawnRadius = 15;
	
	/** A list of all boids managed by this BoidManager */
	internal List<Boid> boidList = new List<Boid>();
	/** Average position of all Boids in the flock. */
	internal Vector3 boidCenter;
	/** Average velocity of all Boids in the flock. */
	internal Vector3 boidVelocity;
	
	/**
	*	Start method is called on initialization.
	*	Creates boids in the scene inside the BoidManager's collider.
	*	Uses the numBoids variable to determine how many boids to create.
	*/
	void Start () 
	{
		for(int i = 0; i < numBoids; i++) 
		{
			Boid boid = Instantiate(prefab, transform.position, transform.rotation) as Boid;
			boid.transform.parent = transform;
			//generate random values for theta and phi
			float theta = 2 * Mathf.PI * Random.value;
			float phi = Mathf.Acos(2 * Random.value - 1);
			//calculate a point on the surface of the sphere. Then multiply by a value 0-1 to get a point inside the sphere (not just surface).
			//x = r * sin(theta) * cos(phi)
			//y = r * sin(theta) * sin(phi)
			//z = r * cos(theta)
			boid.transform.position += new Vector3(	Random.value * spawnRadius * Mathf.Sin(theta) * Mathf.Cos(phi),
																	Random.value * spawnRadius * Mathf.Sin(theta) * Mathf.Sin(phi),
																	Random.value *spawnRadius * Mathf.Cos(theta));
			boid.controller = this;
			boidList.Add(boid);
		}
	}
	
	/**
	*	Called every frame by the Unity Engine. 
	*	Recalculates the center and average velocity of the flock.
	*/
	void Update()
	{
		boidCenter = Vector3.zero;
		boidVelocity = Vector3.zero;
		
		foreach(Boid b in boidList)
		{
			boidCenter += b.rigidbody.position;
			boidVelocity += b.rigidbody.velocity;
		}
		
		boidCenter /= boidList.Count;
		boidVelocity /= boidList.Count;
	}
	
	/**
	*	Removes a Boid from the boidList.
	*/
	public void removeBoid(Boid b)
	{
		boidList.Remove(b);
	}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class World : MonoBehaviour 
{
	public float xMin = -180.0f;
	public float xMax = 180.0f;
	public float yMin = 20.0f;
	public float yMax = 180.0f;
	public float zMin = -180.0f;
	public float zMax = 180.0f;
	
	void Start()
	{
		if(tag == "" || tag == null)
			tag = "WorldData";
	}
	
	/**
	*	Checks if any Boids remain in the scene. If there are none, the method restarts the program.
	*/
	public void checkRestart()
	{
		GameObject[] flocks = GameObject.FindGameObjectsWithTag("BoidManager");
		bool boidsLeft = false;
		
		foreach(GameObject flock in flocks)
		{
			BoidManager bmanager = flock.GetComponent<BoidManager>();
			if(bmanager.boidList.Count > 0)
				boidsLeft = true;
		}
		
		if(!boidsLeft)
			Application.LoadLevel(Application.loadedLevel);
	}
}