在需要对一定数量物体(每一物体拥有自己思路行为)进行同一操作时,为了不使展现手法过于机械化,使其更符合自然情况时可使用群组行为算法。
简单举例,在Unity中实现一群鸟儿朝向同一目标飞去时,如果直接控制行动则会显得过于僵硬且每一只鸟儿的路径一致,如下图
此种情况下我们使用群组行为算法,使其每一只鸟儿有自己的思路,与同伴过近则分离,过远则聚合,且始终保持是一个群体的行动。
在游戏中我们用于改变物体移动即是通过物体的速度方向来进行改变,而速度的改变取决于加速度,加速度可由力来得到,针对于上述三种情况可分为三种力。
1.分离力,当周围一定范围内的同类过多时,对其施加一个远离的力,力的方向为与其他近距离连线向量的和向量的反方向
foreach(GameObject item in 周围物体集合)
{
Vector3 dir = transform.position - item.transform.position;//力的方向
separateForce += dir.normalized / dir.magnitude;//力的大小,距离越近时力越大
}
2.队列的力,即是无论怎么飞都要保持与其他同类形成一个整体,群体对个体有限制的力,力的方向为所有同类中心点与自身连线的向量减去当前朝向方向的向量方向
Vector3 avgDir = Vector3.zero;//队伍整体方向
foreach (GameObject item in 大范围内同类集合)
{
avgDir += item.transform.forward;
}
avgDir /= 集合内同类个数;
队列力 = avgDir - transform.forward;//偏差越大力越大
3.聚集的力,当离队伍过远时产生的力,力的方向为由自身指向周围同类中心点的方向
//if (周围物体集合成员数 > 0 || 大范围内集合成员数 <= 0) return;//当进行分离操作或周围无同类物体时不产生聚集的力
if (大范围内集合成员数 > 0)//分离的力与聚集的力同时作用
{
Vector3 center = Vector3.zero;
foreach (GameObject item in 大范围集合)
{
center += item.transform.position ;
}
center /= alignmentNeighbors.Count;
Vector3 DirToCenter = center - this.transform.position;
聚集力 += DirToCenter;
聚集力 *= gatherWeight;//权重
sumForce += 聚集力;
}
由于在飞行途中会产生力的较大变化,此时我们需让这个力保持相对的恒定
Vector3 engineForce = 当前速度.normalized * 初始速度.magnitude;
sumForce += engineForce * 0.1f;
有了群组行为算法后我们再让这些鸟儿飞向一个目标,完整代码如下
using System.Collections.Generic;
using UnityEngine;
public class CrowAI : MonoBehaviour
{
private int m = 1;//质量
public float speed = 3;
private Vector3 velocity = Vector3.forward*5;
private Vector3 startVelocity;
public Transform target;
private Vector3 sumForce = Vector3.zero;//合力
private Vector3 separateForce = Vector3.zero;//分离的力
public List<GameObject> separateNeighbors = new List<GameObject>();
private int separationDistance = 3;
public float separationWeight = 1;//分离力权重
private Vector3 alignmentForce = Vector3.zero;//队列的力
public List<GameObject> alignmentNeighbors = new List<GameObject>();
private int alignmentDistance = 6;
public float alignmentWeight = 1;//队列力权重
private Vector3 gatherForce = Vector3.zero;//聚集的力
public float gatherWeight = 1;//聚集力权重
private float checkIntarval = 0.2f;//检测间隔
private void Start()
{
target = GameObject.Find("target").transform;
startVelocity = velocity;
InvokeRepeating("CalcForce", 0, checkIntarval);
}
void CalcForce()
{
sumForce = Vector3.zero;
separateForce = Vector3.zero;
alignmentForce = Vector3.zero;
gatherForce = Vector3.zero;
//计算分离的力
separateNeighbors.Clear();
Collider[] colliders = Physics.OverlapSphere(this.transform.position, separationDistance);//获得周围的物体
foreach (Collider item in colliders)
{
if(item!=null&&item.gameObject!=this.gameObject)
{
separateNeighbors.Add(item.gameObject);
}
}
foreach(GameObject item in separateNeighbors)
{
Vector3 dir = transform.position - item.transform.position;
separateForce += dir.normalized / dir.magnitude;
}
if(separateNeighbors.Count>0)
{
separateForce *= separationWeight;
sumForce += separateForce;
}
//计算队列的力
alignmentNeighbors.Clear();
colliders = Physics.OverlapSphere(transform.position, alignmentDistance);
foreach (Collider item in colliders)
{
if(item!=null&&item.gameObject!=this.gameObject)
{
alignmentNeighbors.Add(item.gameObject);
}
}
Vector3 avgDir = Vector3.zero;
foreach (GameObject item in alignmentNeighbors)
{
avgDir += item.transform.forward;
}
if(alignmentNeighbors.Count>0)
{
avgDir /= alignmentNeighbors.Count;
alignmentForce = avgDir - transform.forward;
alignmentForce *= alignmentWeight;
sumForce +=alignmentForce;
}
//计算聚集的力
//if (separateNeighbors.Count > 0 || alignmentNeighbors.Count <= 0) return;//当进行分离操作或周围无同类物体时不产生聚集的力
if (alignmentNeighbors.Count > 0)
{
Vector3 center = Vector3.zero;
foreach (GameObject item in alignmentNeighbors)
{
center += item.transform.position ;
}
center /= alignmentNeighbors.Count;
Vector3 DirToCenter = center - this.transform.position;
gatherForce += DirToCenter;
gatherForce *= gatherWeight;
sumForce += gatherForce;
}
//保持恒定飞行速度的力
Vector3 engineForce = velocity.normalized * startVelocity.magnitude;
sumForce += engineForce * 0.1f;
//朝目标飞行
Vector3 targetDir = target.position - transform.position;
sumForce += (targetDir.normalized - transform.forward) * speed;
}
private void Update()
{
Vector3 a = sumForce / m;//计算加速度
velocity += a * Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(velocity), Time.deltaTime * 3);
transform.Translate(transform.forward * Time.deltaTime * velocity.magnitude, Space.World);
}
}
此时这些鸟儿的行动则有了自己的思维