1、模仿群体行为需要下面几种操控行为
分离(Separation):避免个体在局部过于拥挤的操控力
队列(Alignment):朝向附近同伴的平均朝向的操控力
聚集(Cohesion):像附近同伴的平均位置移动的操控力
2、检测附近AI角色
为了实现组行为,首先需要检测位于当前AI角色“邻域”中的其他AI角色,用一个雷达脚本实现:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Radar : MonoBehaviour { //碰撞体的数组 private Collider[] colliders; //计时器 private float timer = 0; //邻居列表 public List<GameObject> neighbors; //无需每帧进行检测,该变量设置检测的时间间隔 public float checkInterval = 0.3f; //设置邻域半径 public float detectRadius = 10f; //设置检测哪一层的游戏对象 public LayerMask layersChecked; // Use this for initialization void Start () { //初始化邻居列表 neighbors = new List<GameObject>(); } // Update is called once per frame void Update () { timer += Time.deltaTime; //如果距离上次检测的时间大于所设置的检测时间间隔,那么再次检测; if (timer > checkInterval) { //清除邻居列表 neighbors.Clear(); //查找当前AI角色邻域内的所有碰撞体 colliders = Physics.OverlapSphere(transform.position, detectRadius, layersChecked); //对于每个检测到的碰撞体,获取Vehicle组件,并且加入邻居列表中; for(int i = 0; i < colliders.Length; i++) { if (colliders[i].GetComponent<Vehicle>()) neighbors.Add(colliders[i].gameObject); } //计时器归0 timer = 0; } } }
3、与群中邻居保持适当距离——分离
为了计算分离行为所需的操控力,首先要搜索指定领域内的其他邻居,然后对每个邻居,计算AI角色到该邻居的向量r,将向量r归一化(r/|r|,这里|r|表示向量r的长度),得到排斥力的方向,由于排斥力的大小与距离成反比的,因此还需要除以|r|,得到该邻居对它的排斥力,即r/|r|2,然后把来自所有邻居的排斥力相加,就得到了分离行为的总操控力
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForSeparation : Steering { //可接受距离 public float comfortDistance = 1; //当AI角色与邻居之间距离过近时的惩罚因子; public float multiplierInsideComfortDistance = 2; // Use this for initialization void Start () { } public override Vector3 Force() { Vector3 steeringForce = new Vector3(0, 0, 0); //遍历这个AI角色的邻居列表中的每个邻居 foreach(GameObject s in GetComponent<Radar>().neighbors) { //如果s不是当前AI角色 if ((s != null) && (s != this.gameObject)) { //计算当前AI角色与邻居s之间的距离 Vector3 toNeighbor = transform.position - s.transform.position; float length = toNeighbor.magnitude; //计算这个邻居引起的操控力(可以认为是排斥力,大小与距离成反比) steeringForce += toNeighbor.normalized / length; //如果二者之间距离大于可接受距离,排斥力再乘以一个额外因子 if (length < comfortDistance) steeringForce *= multiplierInsideComfortDistance; } } return steeringForce; } // Update is called once per frame void Update () { } }
4、与群中邻居朝向一致——队列
using System.Collections; using System.Collections.Generic; using UnityEngine; /* * 与群中邻居朝向一致——队列 *通过迭代所有邻居,可以求出AI角色朝向向量的平均值以及速度的平均值 * 得到想要的朝向 * 然后减去AI角色的当前朝向,就可以得到队列操控力 */ public class SteeringForAlignment : Steering { // Use this for initialization void Start () { } public override Vector3 Force() { //当前AI角色的邻居的平均朝向 Vector3 averageDirection = new Vector3(0, 0, 0); //邻居的数量 int neighborCount = 0; //遍历当前AI角色的所有邻居 foreach (GameObject s in GetComponent<Radar>().neighbors) { //如果s不是当前AI角色 if ((s != null) && (s != this.gameObject)) { //将s的朝向向量加到averageDirection之中; averageDirection += s.transform.forward; //邻居数量加1 neighborCount++; } } //如果邻居数量大于0 if (neighborCount > 0) { //将累加得到朝向向量除以邻居的个数,求出平均朝向向量 averageDirection /= (float)neighborCount; //平均朝向向量减去当前朝向向量,得到操控向量 averageDirection -= transform.forward; } return averageDirection; } // Update is called once per frame void Update () { } }
5、成群聚集在一起——聚集
using System.Collections; using System.Collections.Generic; using UnityEngine; /* 成群聚集在一起——聚集 * 聚集行为产生一个使AI角色移向邻居的质心的操控力 * 迭代所有邻居求出AI角色位置的平均值,利用靠近行为,将这个平均值作为目标位置 */ public class SteeringForCohesion : Steering { private Vector3 desiredVelocity; private Vehicle m_vehicle; private float maxSpeed; // Use this for initialization void Start () { m_vehicle = GetComponent<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; } public override Vector3 Force() { //操控向量 Vector3 steeringForce = new Vector3(0, 0, 0); //AI角色的邻居的质心,即平均位置 Vector3 centerOfMass = new Vector3(0, 0, 0); //AI角色的邻居数量 int neighborCount = 0; //遍历邻居列表中的每个邻居 foreach(GameObject s in GetComponent<Radar>().neighbors) { //如果s不是当前AI角色 if ((s != null) && (s != this.gameObject)) { //累加s的位置 centerOfMass += s.transform.position; //邻居数量加1 neighborCount++; } } //如果邻居数量大于0 if (neighborCount > 0) { //将位置的累加值除以邻居数量,得到平均值 centerOfMass /= (float)neighborCount; //预期速度为邻居位置平均值与当前位置之差 desiredVelocity = (centerOfMass - transform.position).normalized * maxSpeed; //预期速度减去当前速度,求出操控向量 steeringForce = desiredVelocity - m_vehicle.velocity; } return steeringForce; } // Update is called once per frame void Update () { } }