群组行为说的是一群人,或物执行一样的东西,比如说走路,但是不能很规律的执行,有人工智能的感觉的去执行走路,更加真实的去模拟现实,变得更加符合实际情况。
这里我就模拟了一下群组走路的一个情况,为了避免有的小伙伴的密集恐惧症,这里我就不上效果图了,大家自行看脚本,编写一下试试看吧,下面先贴出完整脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AIGroupMath : MonoBehaviour {
//质量
public float m = 1f;
//加速度
public Vector3 velocity = Vector3.zero;
//函数调用间隔
public float time = 0.2f;
//运动速度
public float speed = .1f;
//目标点
public Transform target;
//前进方向
public Vector3 sumDir = Vector3.zero;
//分离范围
public float separationScop = 1.5f;
//分离方向
public Vector3 separationDir = Vector3.zero;
//分离范围内的所有成员
public Collider[] separationNeghbar;
//分离比重
public float separetionWeigth = 1f;
//聚合范围
public float cohisionScop = 6f;
//聚合方向
public Vector3 cohisionDir = Vector3.zero;
//聚合范围内的所有成员
public Collider[] cohisionNeghbar;
//聚合比重
public float cohisionWeigth = 1f;
//队列范围
public float alignmentScop = 3f;
//队列范围内的成员
public Collider[] alignmentNeghbar;
//队列方向
public Vector3 alignmentDir = Vector3.zero;
//队列比重
public float alignmentWeigth = 1f;
//队列成员方向和
public Vector3 alignmentsDir = Vector3.zero;
private void Start()
{
target = GameObject.Find("Target").transform;
InvokeRepeating("GetSumDir", 0, time);
}
/// <summary>
/// 得到一个总的方向
/// </summary>
public void GetSumDir()
{
//进入清空方向
separationDir = Vector3.zero;
cohisionDir = Vector3.zero;
alignmentDir = Vector3.zero;
sumDir = Vector3.zero;
alignmentsDir = Vector3.zero;
//分离方向
separationNeghbar = Physics.OverlapSphere(transform.position, separationScop);
if (separationNeghbar.Length > 1)
{
foreach (Collider n in separationNeghbar)
{
if (n.gameObject != gameObject)
{
separationDir += (transform.position - n.transform.position).normalized;
}
}
sumDir += (separationDir * separetionWeigth);
}
//聚合方向
if (separationNeghbar.Length <= 1)
{
Vector3 center = Vector3.zero;
cohisionNeghbar = Physics.OverlapSphere(transform.position, cohisionScop);
if (cohisionNeghbar.Length > 1)
{
foreach (Collider n in cohisionNeghbar)
{
center += n.transform.position;
}
center /= cohisionNeghbar.Length;
}
cohisionDir += (center - transform.position);
sumDir += (cohisionDir *= cohisionWeigth);
}
//队列方向
alignmentNeghbar = Physics.OverlapSphere(transform.position, alignmentScop);
if (alignmentNeghbar.Length > 1)
{
foreach (Collider n in alignmentNeghbar)
{
if (n.gameObject != gameObject)
{
alignmentsDir += n.transform.forward;
}
}
alignmentsDir /= (alignmentNeghbar.Length - 1);
alignmentDir = (alignmentsDir - transform.forward);
sumDir += (alignmentDir * alignmentWeigth);
}
//目标点方向
sumDir += ( (target.position - transform.position).normalized - transform.forward) * speed;
}
private void FixedUpdate()
{
//公式F = ma; 所以 a = F/m
Vector3 a = sumDir / m;
velocity += a * Time.deltaTime;
transform.rotation = Quaternion.LookRotation(velocity);
transform.Translate(velocity * Time.deltaTime, Space.World);
}
}
以上是完整的Unity群组行为的代码,接下来开始分析每一个力,每一个方向:
//分离方向
separationNeghbar = Physics.OverlapSphere(transform.position, separationScop);
if (separationNeghbar.Length > 1)
{
foreach (Collider n in separationNeghbar)
{
if (n.gameObject != gameObject)
{
separationDir += (transform.position - n.transform.position).normalized;
}
}
sumDir += (separationDir * separetionWeigth);
}
因为群组行为是由三大要素,分离,聚合,队列组合得到的,所以先说这三个要素,第一个就是分离,也就是上面的这一小段脚本,首先,结合实际情况,什么时候分离,举个例子:大街上,人山人海,为了不踩到别人,我们要和别人保持一个相对范围,如果越过了这个范围,我们就很有可能踩到别人或者被别人踩,那我们保持的这个距离就是分离脚本中的范围,也就是说,我们在这个范围内是不可以有人的,有人的话会出事故,所以,第一行脚本做的操作就是在确定设定范围内的物体有哪些,physics.OverlapSphere()这个函数的作用是得到设定范围内具有圆形碰撞器的物体,我们的每一个物体上面都会有一个圆形碰撞器,当然,也可以换成别的碰撞器,对应的函数也在physics下。
得到这个范围内的物体后,我们去把符合条件物体的向量全部加起来,得到最终的一个Want方向,这个want就是我们想要行走的方向,transform.position - n.transform.position 这个方向就是本身和当前符合条件的物体相减得出与这个物体的分离朝向,之后的都会计算,最后把这些朝向相加,就得出我们想要的Want朝向了。
//聚合方向
if (separationNeghbar.Length <= 1)
{
Vector3 center = Vector3.zero;
cohisionNeghbar = Physics.OverlapSphere(transform.position, cohisionScop);
if (cohisionNeghbar.Length > 1)
{
foreach (Collider n in cohisionNeghbar)
{
center += n.transform.position;
}
center /= cohisionNeghbar.Length;
}
cohisionDir += (center - transform.position);
sumDir += (cohisionDir *= cohisionWeigth);
}
第二个,聚合方向,聚合与分离是相对应的,当我们分离时是不会执行聚合的,聚合时也是不会执行分离的 ,所以,判断一下当前是否在分离,不在的话执行聚合代码块。
聚合是说大家现在分散的比较开,向中间靠拢一下,这就是我们想要的朝向,中间点Center,设定一个范围,切记,分离的范围要比聚合的范围小,最好的方式是以2倍的范围,那么,在这个范围内,得到符合条件的物体,之后把每一个物体的位置信息相加,然后除以物体个数,得出来的就是中心点Center了,下一步就是让朝向从当前的朝向变成向Center的朝向了,所以向量减法得出最终聚合朝向,cohisionDir += (center - transform.position),这里我用+=加了朝向,其实是不用的,每一次进入函数都会把所有的朝向清零,用=也是可以的。
//队列方向
alignmentNeghbar = Physics.OverlapSphere(transform.position, alignmentScop);
if (alignmentNeghbar.Length > 1)
{
foreach (Collider n in alignmentNeghbar)
{
if (n.gameObject != gameObject)
{
alignmentsDir += n.transform.forward;
}
}
alignmentsDir /= (alignmentNeghbar.Length - 1);
alignmentDir = (alignmentsDir - transform.forward);
sumDir += (alignmentDir * alignmentWeigth);
}
最后一个,队列方向,何为队列,形象的来说就是一个整体队伍在跑步,教官说,保持队列!意思就是快的等等慢点,慢的追追快的,整个队伍速度一致,方向一致,无人掉队,也无人突出,这就是队列。
所以,用脚本把这个思想实现出来就是,把符合条件物体的朝向全部相加,最后除以物体数量,这个就是平均的朝向了,然后知道了平均向量,自身向量也知道,相减得出方向值,这个就是队列的方向了。
把聚集,分离,队列的方向相加,得出的就是我们想要的最终群组行为的朝向了。
这里有几个点要说一下,Physics.OverlapSphere这个函数会把自身也算进去,所以我在判断物体时都是length-1,因为聚集的中心点要把自己也算进去,所以直接除以的是length,队列方向不需要自身,毕竟本来就是自身朝向的调整,所以就不能算自身了。同时,队列和聚集的计算中都没有把自身算进去。
最后,如果说对向量不理解的小伙伴,不知道为什么要减位置,减朝向的,可以看看我的另外一篇文章
https://blog.csdn.net/qq_38186269/article/details/81396571
Unity开发中常用的基础3D数学(向量,点乘,叉乘,矩阵,四元数,欧拉角)