U3d_人工智能[二]之鸟群行为

版权声明:本文为博主原创文章,未经博主允许不得转载。 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);
    }
}

猜你喜欢

转载自blog.csdn.net/u3d_20171030/article/details/79626575