
目录
正式开始之前,我们先来搞懂一下究竟什么叫Flocking算法?
Flocking algorithm 国内一般称为蜂拥算法,由许多离散的动物形成,但群体整体上是流动的,这是个体行为的综合结果。
典型的自然现象包括:蜂群、鸟群、鱼群、兽群等,这些动物聚集的现象(包括人类)可以帮助生物更好的躲避天敌、迁徙、获取食物......
下面放几张图片:
野兽迁徙

狼群捕食

蜂群制造蜂蜜

大雁南飞

鱼群移动

1987 年 7 月,Craig Reynolds这位老先生率先提出了经典的Flocking模型,该模型要求群体行为满足三个规则:
聚合:独立的个体逐渐加入到群体
速度匹配:个体与群体的航向保持一致,不要脱离
分离:避免群体内的个体相互碰撞
下面主要在Unity3D中,实现一个简易的鱼群模拟,实现了生成鱼群、聚合鱼群、速度匹配、捕食、分离等功能模块,下面来介绍一下。
为了,更好的管理鱼群,我们在脚本中定义了一个组件。
[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;//转速
在脚本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;//两个脚本间联系
}
}
对于鱼群的移动,要在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));
}

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

为了避免鱼群内部的鱼相互碰撞,要指定一个分离的原则。
如上图所示,鱼本来要向中心位置聚合的,但由于会碰撞到其他的鱼,所以需要转向。
🐟和🐟之间的躲避变量是avoid,代码部分也在上面的Community()函数里面了。
一开始,我们定义了一个10*10*10的边界还记得吗?
为了防止鱼游动超出边界,我们要定义一个转向,让鱼即将超出边界时,掉头向着中心方向移动。
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;
鱼群在移动的过程中,可以会遇到障碍物、捕食者,这时候需要鱼群做出快速避障的功能。
初中的物理知识,入射角=反射角,我们需要鱼的脑子上面向前发射一条长长的射线,碰到障碍物的时候。

原本的方向等于反射的光线。
判断转向
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算法是群居动物的合作行为,通过个体间的相互作用实现整体的目标。本文实现的只是一个简单的🐟集群模拟,仅仅是模式识别领域数据聚类的一个算法分支,转向角度、视野、领队方面还没有实现。
总之,非常感谢大家耐着性子阅读这篇长文( ̄︶ ̄)。
【参考资料】
如果我使用ruby版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更
其实做自媒体的成本并不高,入门只需要一部手机即可!在手机上找视频素材、使用手机剪辑视频、最后使用手机发布视频作品获得收益!方法并不难,今天这期内容就来给粉丝们分享一种小方法,每天稳定收益100-300,抓紧点赞收藏!1、找素材(1)使用手机拍摄自己喜欢的经典段落,使用程序把文案内容提取出来(2)也可以在豆瓣、知乎、微博等网站中找一些自己需要的文案素材(3)把文案进行润色修改,可以加入一些自己的观点(4)视频素材可以使用软件中自带的素材,也可以在素材网站中下载完整版的素材2、文案配音(1)把复制好的文案直接导入小程序中(2)调整音色、音调后一键合成音频即可(3)可以选择自己朗读配音,需要花一点时
目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非
-if!request.path_info.include?'A'%{:id=>'A'}"Text"-else"Text"“文本”写了两次。我怎样才能只写一次并同时检查path_info是否包含“A”? 最佳答案 有两种方法可以做到这一点。使用部分,或使用content_forblock:如果“文本”较长,或者是一个重要的子树,您可以将其提取到一个部分。这会使您的代码变干一点。在给出的示例中,这似乎有点矫枉过正。在这种情况下更好的方法是使用content_forblock,如下所示:-if!request.path_info.inc
我有这个代码:context"Visitingtheusers#indexpage."dobefore(:each){visitusers_path}subject{page}pending('iii'){shouldhave_no_css('table#users')}pending{shouldhavecontent('Youhavereachedthispageduetoapermissionic错误')}它会导致几个待处理,例如ManagingUsersGivenapractitionerloggedin.Visitingtheusers#indexpage.#Noreason
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?
(跟进我之前的问题,Ruby:howcanIcopyavariablewithoutpointingtothesameobject?)我正在编写一个简单的Ruby程序来在.svg文件中进行一些替换。第一步是从文件中提取信息并将其放入数组中。为了避免每次调用此函数时都从磁盘读取文件,我尝试使用memoize设计模式-在第一次调用后的每次调用中都使用缓存结果。为此,我使用了一个在函数之前定义的全局变量。但是,即使我在返回局部变量之前将该变量.dup为局部变量,调用该变量的函数仍在修改全局变量。这是我的实际代码:#memoizetokeepfromhavingtoreadoriginalfi
1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva
我一直在尝试用Ruby实现Luhn算法。我一直在执行以下步骤:该公式根据其包含的校验位验证数字,该校验位通常附加到部分帐号以生成完整帐号。此帐号必须通过以下测试:从最右边的校验位开始向左移动,每第二个数字的值加倍。将乘积的数字(例如,10=1+0=1、14=1+4=5)与原始数字的未加倍数字相加。如果总模10等于0(如果总和以零结尾),则根据Luhn公式该数字有效;否则无效。http://en.wikipedia.org/wiki/Luhn_algorithm这是我想出的:defvalidCreditCard(cardNumber)sum=0nums=cardNumber.to_s.s
下面是我写的一个计算斐波那契数列中的值的方法:deffib(n)ifn==0return0endifn==1return1endifn>=2returnfib(n-1)+(fib(n-2))endend它工作到n=14,但在那之后我收到一条消息说程序响应时间太长(我正在使用repl.it)。有人知道为什么会这样吗? 最佳答案 Naivefibonacci进行了大量的重复计算-在fib(14)fib(4)中计算了很多次。您可以将内存添加到您的算法中以使其更快:deffib(n,memo={})ifn==0||n==1returnnen