版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u3d_20171030/article/details/79626575
1. 鸟群行为,三个力为重点
鸟群向着一个方向移动,在游戏中会给它们一个目标点,在朝着目标点飞行的时候,如果代码仅仅是让鸟群朝向目标点,然后向前移动,就会导致鸟群拥挤,特别是到达目标点之后鸟群会聚集在一起.
我们要实现的效果是:
1.鸟群不会聚集到一起(分离力)
2.鸟群每个个体的方向不会一模一样,但是总体方向是向目标点移动的(队列力)
3.个体鸟不会离大队伍太远(聚集力)
以及一些其他的行为.
既然是用力来影响速度,那就要用到牛顿第二定律:F=ma;在这里m是我们自己定义大小且不会改变,所以F与a成正比.三个力为分力,最终影响鸟的是这三个力的合力
2. 分离力
public Vector3 velocity=Vector3.forward;//当前速度
public Vector3 sumForce=Vector3.zero;//合力
public float m = 1;//质量
public List<GameObject> separationNeighbors = new List<GameObject> ();//用来存储范围内需要作用分离力的游戏物体
public float separationDistion=3;//分离力检查范围
public Vector3 separationForce=Vector3.zero;//分离力
public float separationWeight = 1;//分离力的权重
分离力由自身与周围鸟的距离决定其大小,方向则是与其它鸟相对方向的反方向.
首先,检查出一定范围内的的鸟有哪些
Collider[] colliders = Physics.OverlapSphere(transform.position, separationDistion);//以第一个参数为球心,以第二个参数为半径发射一个球,返回碰撞到的所有collider
//将获得的所有collider的游戏物体放到列表里面
foreach (Collider c in colliders)
{
if (c != null && c.gameObject != this.gameObject)
{
separationNeighbors.Add(c.gameObject);
}
}
获取到自身周围的鸟之后,每个鸟与自身的距离越小则分离力越大,方向就是又检测到的鸟指向本鸟
//列表里的所有物体都给thisGameObject一个力
foreach (GameObject neighbor in separationNeighbors)
{
Vector3 dir = transform.position - neighbor.transform.position;//此处获取的是一个向量,由目标鸟指向本鸟,长度为两鸟的距离
separationForce += dir.normalized / dir.magnitude;//dir.normalized是将方向归一化(就是方向不变,长度变为1),dir.magnitude就是获取这个向量的长度
}
//分离力改变合力
if (separationNeighbors.Count > 0)
{
separationForce *= separationWeight;
sumForce += separationForce;
}
3. 队列力
public List<GameObject> alignmentNeighbors = new List<GameObject>();//用来储存范围内需要所用队列力的游戏物体
public float alignmentDistance = 6;//队列力的检查范围
public Vector3 aligmentForce=Vector3.zero;//队列力
public float alignmentWeight = 1;//队列力的权重
同样的,先是检查本鸟周围的鸟,不过这次检查的范围需要大一些
colliders = Physics.OverlapSphere(transform.position, alignmentDistance);
foreach (Collider c in colliders)
{
if (c.gameObject != null && c.gameObject != this.gameObject)
{
alignmentNeighbors.Add(c.gameObject);
}
}
再计算出附近鸟的总方向
Vector3 avgDir=Vector3.zero;//储存所有邻居的平均方向
foreach (GameObject neighber in alignmentNeighbors)
{
avgDir += neighber.transform.forward;//把所有邻居方向加起来
}
而本鸟需要调整到总方向,正是用向量的知识--向量A减去向量B获取到的就是一条由B指向A的向量.(附近鸟的方向的合向量的平均值)减去本鸟的方向量,获取到的向量长度越长,则分离力越大
if (alignmentNeighbors.Count > 0)
{
avgDir /= alignmentNeighbors.Count;//平均方向
aligmentForce = avgDir - transform.forward;//计算出队列力(向量知识)
aligmentForce *= alignmentWeight;
sumForce += aligmentForce;
}
4. 聚集力
public Vector3 cohesionForce=Vector3.zero;//聚集力
public float cohesionWeight = 1;//权重
聚集力就直接可以使用队列力的检查范围,同样使用向量的知识获取附近鸟的中心点--附近鸟的方向向量除以附近鸟的个数,就是中心点,我们让本鸟受力向中心点靠近
//聚集力
if (alignmentNeighbors.Count <= 0) return;//如果附近没有邻居就return
Vector3 center = Vector3.zero;//所有邻居的中心点
foreach (GameObject n in alignmentNeighbors)
{
center += n.transform.position;
}
center /= alignmentNeighbors.Count;
Vector3 dirToCenter = center - transform.position;//一条由this指向中心点的向量,向量长度可做为力的大小
cohesionForce += dirToCenter;
sumForce += cohesionForce;
5. 最终效果
每个分力其实效果非常不好,但是都用上之后,效果即可发生质变.再做一些其他的处理,比如设定一个终点叫做Target,让所有鸟的动画开始播放的时间点不一样,就不会显得单调.接下来贴出全代码:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class CrowAI : MonoBehaviour {
public GameObject target;//鸟群目标
public float targetWeight = 1;//目标力权重
public Vector3 velocity=Vector3.forward;//当前速度
public Vector3 sumForce=Vector3.zero;//合力
public float m = 1;//质量
public List<GameObject> separationNeighbors = new List<GameObject> ();//用来存储范围内需要作用分离力的游戏物体
public float separationDistion=3;//分离力检查范围
public Vector3 separationForce=Vector3.zero;//分离力
public float separationWeight = 1;//分离力的权重
public List<GameObject> alignmentNeighbors = new List<GameObject>();//用来储存范围内需要所用队列力的游戏物体
public float alignmentDistance = 6;//队列力的检查范围
public Vector3 aligmentForce=Vector3.zero;//队列力
public float alignmentWeight = 1;//队列力的权重
public Vector3 cohesionForce=Vector3.zero;//聚集力
public float cohesionWeight = 1;//权重
public float checkInterval=0.2f;//检查时间间隔
private Animation animation;//飞行动画
// Use this for initialization
void Start () {
InvokeRepeating("CalcForce", 0, checkInterval);//从0秒以后每隔checkInterval秒就执行一次CalcForce方法
animation = GetComponentInChildren<Animation>();//获取动画
Invoke("PlayerAnim", Random.Range(0, 2f));//在不同的时间开始播放动画
}
/// <summary>
/// 播放动画
/// </summary>
void PlayerAnim()
{
animation.Play();
}
void CalcForce()
{
sumForce = Vector3.zero;
separationForce = Vector3.zero;//分离力
aligmentForce = Vector3.zero;//队列力
cohesionForce = Vector3.zero;//聚集力
separationNeighbors.Clear();
alignmentNeighbors.Clear();
Collider[] colliders = Physics.OverlapSphere(transform.position, separationDistion);//以第一个参数为球心,以第二个参数为半径发射一个球,返回碰撞到的所有collider
//将获得的所有collider的游戏物体放到列表里面
foreach (Collider c in colliders)
{
if (c != null && c.gameObject != this.gameObject)
{
separationNeighbors.Add(c.gameObject);
}
}
//列表里的所有物体都给thisGameObject一个力
foreach (GameObject neighbor in separationNeighbors)
{
Vector3 dir = transform.position - neighbor.transform.position;
separationForce += dir.normalized / dir.magnitude;
}
//分离力改变合力
if (separationNeighbors.Count > 0)
{
separationForce *= separationWeight;
sumForce += separationForce;
}
colliders = Physics.OverlapSphere(transform.position, alignmentDistance);
foreach (Collider c in colliders)
{
if (c.gameObject != null && c.gameObject != this.gameObject)
{
alignmentNeighbors.Add(c.gameObject);
}
}
Vector3 avgDir=Vector3.zero;//储存所有邻居的平均方向
foreach (GameObject neighber in alignmentNeighbors)
{
avgDir += neighber.transform.forward;//把所有邻居方向加起来
}
if (alignmentNeighbors.Count > 0)
{
avgDir /= alignmentNeighbors.Count;//平均方向
aligmentForce = avgDir - transform.forward;//计算出队列力(向量知识)
aligmentForce *= alignmentWeight;
sumForce += aligmentForce;
}
//聚集力
if (alignmentNeighbors.Count <= 0) return;//如果附近没有邻居就return
Vector3 center = Vector3.zero;//所有邻居的中心点
foreach (GameObject n in alignmentNeighbors)
{
center += n.transform.position;
}
center /= alignmentNeighbors.Count;
Vector3 dirToCenter = center - transform.position;//一条由this指向中心点的向量,向量长度可做为力的大小
cohesionForce += dirToCenter;
sumForce += cohesionForce;
target = GameObject.Find("Target").gameObject;
Vector3 targetDir = target.transform.position - transform.position;//获取this指向目标点的向量
sumForce += (targetDir.normalized - transform.forward) * targetWeight ;//目标所在位置影响合力
}
// Update is called once per frame
void Update () {
Vector3 a = sumForce / m;//加速度
velocity += a * Time.deltaTime;
//让乌鸦的方向跟速度方向一致,这样不同的速度方向就会出现各种变动了,参数一:this朝向;参数二:this需要改变成的方向,缓慢改变朝向
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(velocity), Time.deltaTime);;
transform.Translate (velocity * Time.deltaTime, Space.World);
}
}