凝聚(cohesion):群组中心保持最小距离
boid是Craig Reynold创造的术语,用以表示类似小鸟这样的对象。我们将使用这个术语描述群组中的每个个体对象。UnityFlock这个脚本控制群组中每一个boid的行为。
using UnityEngine;
using System.Collections;
public class UnityFlock : MonoBehaviour {
public float minSpeed = 20;//最小移动速度
public float turnSpeed = 20;//旋转速度
public float randomFreq = 20;//用来确定更新randomPush变量的次数
public float randomForce = 20;//这个力产生出一个随机增长和降低的速度,并使得群组的移动看上去更加真实
//alignment variables列队变量
public float toOriginForce = 50;//用这个来保持所有boid在一个范围内,并与群组的原点保持一定距离
public float toOriginRange = 100;//群组扩展的程度
public float gravity = 2;
//seperation variables分离变量
public float avoidanceRadius = 50;
public float avoidanceForce = 20;//这两个变量可以让每个boid个体之间保持最小距离
//cohesion variables凝聚变量,这两个变量可用来与群组的领导者或群组的原点保持最小距离。
public float followVelocity = 4;
public float followRadius = 40;
//这些变量控制了boid的移动
private Transform origin;//设为父对象,以控制整个群组中的对象。
private Vector3 velocity;
private Vector3 normalizeedVelocity;
private Vector3 randomPush;//更新基于randomFore的值
private Vector3 originPush;
//以下两个变量存储相邻boid的信息,当前boid需要知道群组中其他boid的信息
private Transform[] objects;
private UnityFlock[] otherFlocks;
private Transform transformComponent;
/*
*/
void Start () {
randomFreq = 1.0f / randomFreq;
//将父类指定为origin
origin = transform.parent;
//Flock transform
transformComponent = transform;
//Temporary components临时
Component[] tempFlocks = null;
//Get all the unity flock omponents from the parent transform in the group
if (transform.parent )
{
tempFlocks = transform.parent.GetComponentsInChildren();
}
//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 flok leader will be UnityFlockController object
transform.parent = null;
//Calculate random push depends on the random frequency provided
StartCoroutine(UpdateRandom());
}
IEnumerator UpdateRandom()
{
while (true)
{
randomPush = Random.insideUnitSphere * randomForce;
yield return new WaitForSeconds(randomFreq + Random.Range(-randomFreq / 2.0f, randomFreq / 2.0f));
}
}
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 diretional vector(length)
d = forceV.magnitude;
//Add push value if the magnitude,the length of the vetor,is less than followRadius to the leader
if (d < followRadius)
{
//calculate the velocity,the speed of the object,based 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 otherSealgull = otherFlocks[i];
//we normalize the otherSealgull veloity vector to get the direction of movement,then wo set a new veloity
avgVelocity += otherSealgull.normalizeedVelocity * f * followVelocity;
}
}
}
//上述代码实现了分离规则,首先,检查当前boid与其他boid之间的距离,并相应的更新速度,接下来,用当前速度除以群组中的boid的数目,计算出群组的平均速度
if (count > 0)
{
//Calculate the aveage flock veloity(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 flok to the leader
if (d > 0)
{
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.0f);
transformComponent.rotation = Quaternion.LookRotation(velocity);
transformComponent.Translate(velocity * Time.deltaTime, Space.World);
normalizeedVelocity = velocity.normalized;
}
}
现在是时候创建控制器了,这个类会更新自己的位置,这样其他的boid个体对象就知道该去哪里,这个对象由前面的UnityFlock脚本中的origin变量引用而来。
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;
void Start () {
initialPosition = transform.position;
CalculateNextMovementPoint();
}
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();
}
}
/*
在我们的Update()方法中,检查控制器对象是否在最终目标位置附近,如果在,使用我们刚刚讨论过的CalculatNextMovementPoint()方法再次更新nextMovementPoint变量
*/
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);
}
}