群组行为是指多个对象组队同时行进的情况,我们可以坐下来,告诉每一个对象它该如何移动,但这样做的工作量太大。取而代之的是,我们去创建一个群组的领导,让它来为我们做这些,
这样我们所要做的就只是设置一些规则,然后群组中的boid就会自行组队。在本章中,我们将学习如何在unity3d中实现这种群组行为。
每个boid都可以应用一下三个基本的规则:
分离(seperation):群组中的每个个体都与相邻个体保持一定距离
列队(alignment):群组以相同速度,向相同方向移动
凝聚(cohesion):群组中心保持最小距离
在本节中我们将创建自己的场景,场景里会有一群对象,并使用C#实现群组行为。有两个主要的组成部分:每个boid行为,以及维持并领导整个群组的主要控制者。我们的场景层级如图,在一个名为UnityFlockController的控制器下面有一些boid实体----UnityFlock。每一个UnityFlock实体都是一个boid对象,它们会引用其父对象UnityFlockController实体作为它们的领导者。UnityFlockController将会在到达目标位置后,随机的更新下一个目标位置。UnityFlock是一个预制体,这个预制体仅仅是一个立方体网格,并拥有UnityFlock脚本。我们可以使用任何更有意思的其他的网格标识这个预制体,比如小鸟。
个体行为
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<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 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);
}
}