组行为
组行为的复杂性来源于个体间的交互,并遵守一些简单的规则。
模仿群体行为需要下面几种操控行为
- 分离:避免个体在局部过于拥挤的操控力
- 队列:朝向附近同伴的平均朝向的操控力
- 聚集:向附近的同伴的平均位置移动的操控力
检测附近的AI角色
从上面的几种操控行为可以看出,每种操控行为都取决于附近的同伴。为了实现组行为,首先需要检测当前AI角色附近的其他AI角色,这要用一个雷达脚本来实现。
一般来说,角色的领域由一个距离和一个角度来定义。有时为了简化,用一个圆来定义。
1using 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 LayMask layersChecked;//设置检测哪一层的游戏对象 void Start() { neighbors = new List<GameObject>(); } void Update() { timer += Time.deltaTime; if(timer > checkInterval) { neighbors.Clear(); colliders = Physics.OverlapSphere(transform.position, detectRadius, layersChecked);//查找当前AI角色邻域内的所有碰撞体 for(int i = 0; i < colliders.Length; i++)//对于每个检测到的碰撞体,获取Vehicle组件,并且加入邻居列表钟 { if (colliders[i].GetComponent<Vehicle>()) neighbors.Add(colliders[i].gameObject); } timer = 0; } } }
分离
分离的目的是使角色与周围其他角色保持一定距离。实现时,为了计算分离行为所需的操控力,需要搜索指定领域内的其他邻居,然后对每个邻居计算与角色的距离向量,进行一系列处理得到操控力
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForSeparation : Steering { public float comforDistance = 1;//可接受的距离 public float multiplierInsideComfortDistance = 2;//当AI角色与邻居距离过近时的惩罚因子 public override Vector3 Force() { Vector3 steeringForce = new Vector3(0, 0, 0); foreach(GameObject s in GetComponent<Radar>().neighbors)//遍历这个AI角色的邻居列表中的每个邻居 { if ((s != null) && (s != this.gameObject)) { Vector3 toNeighbor = transform.position - s.transform.position;//计算当前AI角色与邻居s之间的距离 float length=toNeighbor.magnitude; steeringForce += toNeighbor.normalized / length;//计算这个邻居引起的操控力 if (length < comforDistance) steeringForce *= multiplierInsideComfortDistance; } } return steeringForce; } }
队列
队列的目的是保持AI角色的运动朝向与邻居一致
通过迭代所有邻居,可以求出AI角色平均朝向向量以及速度平均向量,得到想要的朝向,然后减去AI当前朝向,就可以得到队列操控力
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForAlignment : Steering { public override Vector3 Force() { Vector3 averageDirection = new Vector3(0, 0, 0);//当前AI角色的邻居平均朝向 int neighborCount = 0; foreach(GameObject s in GetComponent<Radar>().neighbors)//遍历邻居 { if ((s != null) && (s != this.gameObject)) { averageDirection += s.transform.forward; neighborCount++; } } if(neighborCount > 0) { averageDirection/=(float)neighborCount;//求出平均朝向向量 averageDirection -= transform.forward;//求出操控向量 } return averageDirection; } }
聚集
聚集的目的是使多个AI角色聚到一起。实现时,迭代所有邻居求出AI角色位置的平均值,然后利用靠近行为,将这个平均值作为目标位置。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForCohesion :Steering { private Vector3 desiredVelocity; private Vehicle m_vehicle; private float maxSpeed; void Start() { m_vehicle = GetComponent<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; } public override Vector3 Force() { Vector3 steeringForce = new Vector3(0, 0, 0);//操控向量 Vector3 centerOfMass = new Vector3(0, 0, 0);//AI角色的所有邻居的质心 int neighborCount = 0; foreach (GameObject s in GetComponent<Radar>().neighbors)//遍历邻居 { if ((s != null) && (s != this.gameObject)) { centerOfMass += s.transform.position; neighborCount++; } } if (neighborCount > 0) { centerOfMass /= (float)neighborCount;//求出平均位置向量 desiredVelocity = (centerOfMass - transform.position).normalized * maxSpeed;//与其速度为邻居平均位置与当前位置之差 steeringForce = desiredVelocity - m_vehicle.velocity; } return steeringForce; } }