写点什么

InfoQ 极客传媒 15 周年庆征文|海王的鱼塘是怎样炼成的

作者:知心宝贝
  • 2022 年 6 月 17 日
  • 本文字数:4019 字

    阅读完需:约 13 分钟

InfoQ 极客传媒 15 周年庆征文|海王的鱼塘是怎样炼成的

一、引言

正式开始之前,我们先来搞懂一下究竟什么叫 Flocking 算法?


Flocking algorithm 国内一般称为蜂拥算法,由许多离散的动物形成,但群体整体上是流动的,这是个体行为的综合结果。


典型的自然现象包括:蜂群、鸟群、鱼群、兽群等,这些动物聚集的现象(包括人类)可以帮助生物更好的躲避天敌、迁徙、获取食物......


下面放几张图片:


野兽迁徙



狼群捕食



蜂群制造蜂蜜



大雁南飞



鱼群移动



二、发展


1987 年 7 月,Craig Reynolds这位老先生率先提出了经典的 Flocking 模型,该模型要求群体行为满足三个规则:


  • 聚合:独立的个体逐渐加入到群体

  • 速度匹配:个体与群体的航向保持一致,不要脱离

  • 分离:避免群体内的个体相互碰撞

三、鱼群

下面主要在 Unity3D 中,实现一个简易的鱼群模拟,实现了生成鱼群、聚合鱼群、速度匹配、捕食、分离等功能模块,下面来介绍一下。

1.组件

为了,更好的管理鱼群,我们在脚本中定义了一个组件。


    [Header("Fish Setting")]//控制面板    [Range(0.0f,5.0f)]    public float min;//速度最小值    [Range(0.0f, 5.0f)]    public float max;//速度最小值    [Range(1.0f, 10.0f)]    public float neighborDistance;//聚合的距离    [Range(0.0f, 5.0f)]    public float RotationSpeed;//转速
复制代码



2.生成鱼群

在脚本 Create 里面要定义一个范围,让数组里面的鱼群在这个范围内生成、移动。


    public GameObject prefab1;//🐟种类1    public GameObject prefab2;//🐟种类2    public int fishnum=50;//初始化🐟数量    public GameObject[] fish;//数组存储    public Vector3 swimlimt = new Vector3(5, 5, 5);//边界10*10*10
复制代码


生成的话,我们采取随机生成,范围还是固定在边界范围内部。


    public void Start()    {        fish = new GameObject[fishnum];        for(int i=0;i<fishnum;i++)        {            Vector3 pos = this.transform.position + new Vector3(Random.Range(-swimlimt.x,swimlimt.x),                                                                Random.Range(0, swimlimt.y),                                                                Random.Range(-swimlimt.z, swimlimt.z));            if(i%2==0)                fish[i] = (GameObject)Instantiate(prefab1,pos,Quaternion.identity);//克隆🐟            else                fish[i] = (GameObject)Instantiate(prefab2, pos, Quaternion.identity);            fish[i].GetComponent<FlockSpeed>().sp = this;//两个脚本间联系        }    }
复制代码

3.鱼群运动

对于鱼群的移动,要在 FlockSpeed 脚本里面添加速度、方向。


    private void Update()    {        speed = Random.Range(sp.min, sp.max);//速度范围        this.transform.Translate(0, 0, speed * Time.deltaTime);//开始移动      }
复制代码



这时候,鱼群只会朝向 Z 轴移动,其他的什么也完成不了,下面的步骤才是 Flocking 算法应用的关键。

四、聚合

聚合是 Flocking 算法的关键,这个算法会把鱼聚集到一起。



还记得我们之前组件定义的变量 neighborDistance,这个是聚合的距离。假如两只鱼之间的距离<=neighborDistance,那么它就属于这个集群,我们要想办法把这只鱼加到集群里面来。


那如何让加入的鱼满足整体,不至于脱离呢?这时候就需要鱼群的中心位置,也叫平均位置。


对于一个鱼群来说,每一个🐟都有一个特定的位置,不可能出现两只🐟重合的情况,所以:


平均位置=鱼群位置相加的总和/鱼群的数量



红色星星就是计算的中心位置,最下面的🐟neighborDistance,但满足如果还不修正位置,它就会跑出集群了。向量都学过吧,对于一个三维的坐标,上图红色箭头的向量等于坐标:


average-desired


所以,这只鱼需要转向中心位置,转向的时候还需要注意不要碰到别的鱼,这部分留到分离的时候细讲。


void Community()    {        GameObject[] gos=sp.fish;        float Distance;        Vector3 center = Vector3.zero;        Vector3 avoid = Vector3.zero;        int size=0;        float goSpeed=0.01f;        foreach(GameObject go in gos)        {            if(go!=this.gameObject)//遍历数组内除掉本身所有的鱼            {                Distance = Vector3.Distance(this.transform.position, go.transform.position);//相邻两条鱼距离                if (Distance<=sp.neighborDistance)//满足集群规则                {                    size++;                    center+= go.transform.position;
if (Distance < limt)//避免碰撞 { avoid += this.transform.position - go.transform.position; } FlockSpeed flockSpeed = go.GetComponent<FlockSpeed>(); goSpeed += flockSpeed.speed; } } } if(size>0) { center = center / size+(sp.goal- this.transform.position );//后面加上的是目标位置,朝着目标移动 goSpeed = goSpeed / size;//平均速度 Vector3 direction = (center + avoid) - transform.position; if(direction!=Vector3.zero)//需要转向 { transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), sp.RotationSpeed * Time.deltaTime);//from to 平滑旋转 Quaternion.LookRotation注视旋转 } } }
复制代码

五、速度匹配


如上图所示,每一条🐟都有一个前进的方向。我们要保障所有鱼的速度最终和虚拟领航者一致,那么,对于整个集群来说,虚拟领航者的速度是什么?


虚拟领航者的速度=总体速度和/鱼群数目



如上图所示,current heading 和 average heading 朝向有偏差,所以要调整方向。



调整之后,两个方向保持一致。



最后,集群内部的每条鱼要达到这样的效果。


速度匹配是goSpeed变量,代码部分也在上面的 Community()函数里面了。

六、捕食

鱼群捕食的实现,首先定义一个目标,这个目标有 10%的概率移动,鱼群会跟随目标移动。


private void Update()    {        //10%的几率更换目标        if(Random.Range(0,100)<10)            goal = this.transform.position + new Vector3(Random.Range(-swimlimt.x, swimlimt.x),                                                         Random.Range(0, swimlimt.y),                                                         Random.Range(-swimlimt.z, swimlimt.z));    }
复制代码



为了更加清晰的看见鱼群的移动,我关闭了随机移动,手动更换目标物体的位置。可以看到,鱼群跟随目标的移动而移动。

七、分离

1.躲避🐟


为了避免鱼群内部的鱼相互碰撞,要指定一个分离的原则。


如上图所示,鱼本来要向中心位置聚合的,但由于会碰撞到其他的鱼,所以需要转向。


🐟和🐟之间的躲避变量是avoid,代码部分也在上面的 Community()函数里面了。

2.躲避边界

一开始,我们定义了一个 $101010$的边界还记得吗?


为了防止鱼游动超出边界,我们要定义一个转向,让鱼即将超出边界时,掉头向着中心方向移动。


        Bounds b = new Bounds(sp.transform.position,sp.swimlimt);//边界的盒子        Vector3 direction = Vector3.zero;        RaycastHit hit = new RaycastHit();        if (!b.Contains(transform.position))//即将超出边界        {            turning = true;//开始转向            direction = sp.transform.position - transform.position;//转向方向指向中心位置        }          else            turning = false;
复制代码

3.躲避障碍

鱼群在移动的过程中,可以会遇到障碍物、捕食者,这时候需要鱼群做出快速避障的功能。



初中的物理知识,入射角=反射角,我们需要鱼的脑子上面向前发射一条长长的射线,碰到障碍物的时候。



原本的方向等于反射的光线。


判断转向


        if(Physics.Raycast(this.transform.position,transform.forward*5,out hit))//遇到障碍物        {            //Debug.DrawRay(this.transform.position,this.transform.forward*5,Color.red);红色射线            direction = Vector3.Reflect(this.transform.forward, hit.normal);//反射角 = 入射角 转向            turning = true;        }
复制代码


开始转向


        if(turning)        {            this.transform.rotation = Quaternion.Slerp(transform.rotation,             Quaternion.LookRotation(direction), sp.RotationSpeed * Time.deltaTime);          }        else        {              Community();//聚合函数        }
复制代码

八、效果展示

这里 gif 图片太大了,放不了!!的 gif 图片,我特意把鱼群的目标放到了柱子里面,看看鱼会不会直直的撞向柱子?


鱼一般可以看清 12m 以内的物体,这里我将鱼的视野范围特意扩大了 5 倍。事实上,当鱼脑袋上面发射的红线,碰到海底的这个大柱子时就会发生转向。


但由于目标没有移动,所以鱼群只好围绕着柱子旋转(团队合作捕猎),直到我将目标移出柱子,鱼群才开始远离。

九、总结

Flocking 算法是群居动物的合作行为,通过个体间的相互作用实现整体的目标。本文实现的只是一个简单的🐟集群模拟,仅仅是模式识别领域数据聚类的一个算法分支,转向角度、视野、领队方面还没有实现。


总之,非常感谢大家耐着性子阅读这篇长文( ̄︶ ̄)。


【参考资料】


发布于: 2022 年 06 月 17 日阅读数: 46
用户头像

知心宝贝

关注

公众号:穿越计算机的迷雾 2022.03.07 加入

生于尘埃 溺于人海 死于理想高台

评论 (1 条评论)

发布
用户头像
兄弟们,动动小手点个赞呗
2022 年 06 月 17 日 20:31
回复
没有更多了
InfoQ 极客传媒 15 周年庆征文|海王的鱼塘是怎样炼成的_人工智能_知心宝贝_InfoQ写作社区