1987年Craig W.Reynolds发表一篇名为《鸟群、牧群、鱼群:分布式行为模式》的论文,描述了一种非常简单的、以面向对象思维模拟群体类行为的方法,称之为 Boids ,Boids 采用了三个核心的规则:

  • 排斥性:避免与群体内邻近个体发生碰撞
  • 同向性:趋向与邻近的个体采用相同的速度方向
  • 凝聚向心性:向邻近个体的平均位置靠近

由此我们采用Unity来实现算法并演示,演示结果:

制作思路

每个boid对象,每帧都有2个关键的表:与该boid邻近的boids的表及与该boid最近的boids的表。根据两个表求得一些确定该boid位置、方向、速度的因素(例如其他boids的平均速度,其他boids的平均位置,该boid与其他boid的平均距离等),根据所提出的三规则,设置各影响因素的权重比例,最终所有影响因素加和成为确定的、该boid下一帧的方向、位置、速度。

Boid模型的创建与配置

  1. 创建空对象取名Boid,再创建其空对象子物体,取名Fuselage

    • Fuselage->position(0,0,0),Rotation(7.5,0,0),Scale(0.5,0.5,2)
  2. 创建一个Cube作为Fuselage子物体,移除 Box Collider,并添加拖尾渲染器 TrailRenderer
    • Cube->position(0,0,0),Rotation(45,0,45),Scale(1,1,1)
    • Component->Effects->TrailRenderer,选择材质Defualt-Particle(Material),Time值0.5,End Witdth值0.25
  3. 复制Fuselage创建另一个名为Wing的组件添加到Boid,两个都属于Boid的子物体
  4. 修改主相机位置到顶视图大范围
  5. 将boid设为预制体,boid 模型制作完毕

脚本配置

  • BoidSpawner.cs 绑定于主相机
  • Boid.cs 绑定于预制体Boid上
  • 主相机检视面板中,设定 boidPrefab 变量为预制体 boid

项目地址:Boids

/*-------BoidSpawner.cs-------*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class BoidSpawner : MonoBehaviour
{
//BoidSpawner 的单例模式,只允许存在BoidSpawner的一个实例,所以存放在静态变量S中
static public BoidSpawner S; //配置参数,调整Boid对象的行为
public int numBoids = 100; //boid 的个数
public GameObject boidPrefab; //boid 在unity中的预制体
public float spawnRadius = 100f; //实例化 boid 的位置范围
public float spawnVelcoty = 10f; //boid 的速度
public float minVelocity = 0f;
public float maxVelocity = 30f;
public float nearDist = 30f; //判定为附近的 boid 的最小范围值
public float collisionDist = 5f; //判定为最近的 boid 的最小范围值(具有碰撞风险)
public float velocityMatchingAmt = 0.01f; //与 附近的boid 的平均速度 乘数(影响新速度)
public float flockCenteringAmt = 0.15f; //与 附近的boid 的平均三维间距 乘数(影响新速度)
public float collisionAvoidanceAmt = -0.5f; //与 最近的boid 的平均三维间距 乘数(影响新速度)
public float mouseAtrractionAmt = 0.01f; //当 鼠标光标距离 过大时,与其间距的 乘数(影响新速度)
public float mouseAvoidanceAmt = 0.75f; //当 鼠标光标距离 过小时,与其间距的 乘数(影响新速度)
public float mouseAvoiddanceDsit = 15f;
public float velocityLerpAmt = 0.25f; //线性插值法计算新速度的 乘数
public bool ______________; public Vector3 mousePos; //鼠标光标位置 private void Start()
{
//设置单例变量S为BoidSpawner的当前实例
S = this; //初始化NumBoids(当前为100)个Boids
for (int i = 0; i < numBoids; i++)
Instantiate(boidPrefab);
} private void LateUpdate()
{
//读取鼠标光标位置
Vector3 mousePos2d = new Vector3(Input.mousePosition.x, Input.mousePosition.y, this.transform.position.y); //从世界空间到屏幕空间变换位置
mousePos = this.GetComponent<Camera>().ScreenToWorldPoint(mousePos2d);
}
} /*-------Boid.cs-------*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class Boid : MonoBehaviour
{
static public List<Boid> boids; //实例化Boid 的表 public Vector3 velocity; //当前速度
public Vector3 newVelocity; //下一帧中的速度
public Vector3 newPosition; //下一帧中的位置 public List<Boid> neighbors; //附近所有的 Boid 的表
public List<Boid> collisionRisks; //距离过近的所有 Boid 的表(具有碰撞风险,需要处理)
public Boid closest; //最近的 Boid //初始化Boid
private void Awake()
{
//如果List变量boids未定义,则对其进行定义
if (boids == null)
boids = new List<Boid>(); //向Boids List 中添加Boid
boids.Add(this); //为当前Boid实例提供一个随机的位置和速度
//实例化的boid位置在 半径为 1*spawnRadius 的球形范围内
Vector3 randPos = Random.insideUnitSphere * BoidSpawner.S.spawnRadius; //只让Boid在xz平面上移动,并设定起始坐标
randPos.y = 0;
this.transform.position = randPos; //Random.onUnitSphere 返回 一个半径为1的 球体表面的点
velocity = Random.onUnitSphere;
velocity *= BoidSpawner.S.spawnVelcoty; //初始化两个List
neighbors = new List<Boid>();
collisionRisks = new List<Boid>(); //让this.transform成为Boid游戏对象的子对象
this.transform.parent = GameObject.Find("Boids").transform; //给Boid设置一个随机的颜色
Color randColor = Color.black;
//设置颜色的颜色要 较深,非透明
while (randColor.r + randColor.g + randColor.b < 1.0f)
randColor = new Color(Random.value, Random.value, Random.value);
//渲染 boid
Renderer[] rends = gameObject.GetComponentsInChildren<Renderer>();
foreach (Renderer r in rends)
r.material.color = randColor;
} private void Update()
{
//获取到 当前boid 附近所有的Boids 的表
List<Boid> neighbors = GetNeighbors(this); //使用当前位置和速度初始化新位置和新速度
newVelocity = velocity;
newPosition = this.transform.position; //速度匹配
//取得于 当前Boid 的速度接近的 所有邻近Boid对象 的平均速度
Vector3 neighborVel = GetAverageVelocity(neighbors);
//将 新速度 += 邻近boid的平均速度*velocityMatchingAmt
newVelocity += neighborVel * BoidSpawner.S.velocityMatchingAmt; /*
凝聚向心性:使 当前boid 向 邻近Boid对象 的中心 移动
*/
//取得于 当前Boid 的三位坐标接近的 所有邻近Boid对象 的平均三位间距
Vector3 neighborCenterOffset = GetAveragePosition(neighbors) - this.transform.position;
//将 新速度 += 邻近boid的平均间距*flockCenteringAmt
newVelocity += neighborCenterOffset * BoidSpawner.S.flockCenteringAmt; /*
排斥性:避免撞到 邻近的Boid
*/
Vector3 dist;
if (collisionRisks.Count > 0) //处理 最近的boid 表
{
//取得 最近的所有boid 的平均位置
Vector3 collisionAveragePos = GetAveragePosition(collisionRisks);
dist = collisionAveragePos - this.transform.position;
//将 新速度 += 与最近boid的平均间距*flockCenteringAmt
newVelocity += dist * BoidSpawner.S.collisionAvoidanceAmt;
} //跟随鼠标光标:无论距离多远都向鼠标光标移动
dist = BoidSpawner.S.mousePos - this.transform.position; //若距离鼠标光标太远,则靠近;反之离开(修改新速度)
if (dist.magnitude > BoidSpawner.S.mouseAvoiddanceDsit)
newVelocity += dist * BoidSpawner.S.mouseAtrractionAmt;
else
newVelocity -= dist.normalized * BoidSpawner.S.mouseAvoidanceAmt; //至此在Update()内 确定了 新速度和新位置,需要在后续LateUpdate()内应用
//一般都是Update()内确定参数,在LateUpdate()内实现移动
} private void LateUpdate()
{
//使用线性插值法
//基于计算出的新速度 进而修改 当前速度
velocity = (1 - BoidSpawner.S.velocityLerpAmt) * velocity + BoidSpawner.S.velocityLerpAmt * newVelocity; //确保 速度值 在上下限范围内(超过范围就设定为范围值)
if (velocity.magnitude > BoidSpawner.S.maxVelocity)
velocity = velocity.normalized * BoidSpawner.S.maxVelocity;
if (velocity.magnitude < BoidSpawner.S.minVelocity)
velocity = velocity.normalized * BoidSpawner.S.minVelocity; //确定新位置(附加新方向),相当于1s移动 velocity 的距离
newPosition = this.transform.position + velocity * Time.deltaTime; //将所有对象限制在XZ平面
//修改当前boid的方向:从原有位置看向新位置newPosition
this.transform.LookAt(newPosition); //position移动方式,移动到新位置
this.transform.position = newPosition;
} //查找那些Boid距离当前Boid距离足够近,可以被当作附近对象
public List<Boid> GetNeighbors(Boid boi)
{
float closesDist = float.MaxValue; //最小间距,MaxValue 为浮点数的最大值
Vector3 delta; //当前 boid 与其他某个 boid 的三维间距
float dist; //三位间距转换为的 实数间距 neighbors.Clear(); //清理上次表的数据
collisionRisks.Clear(); //清理上次表的数据 //遍历目前所有的 boid,依据设定的范围值筛选出 附近的boid 与 最近的boid 于各自表中
foreach (Boid b in boids)
{
if (b == boi) //跳过自身
continue; delta = b.transform.position - boi.transform.position; //遍历到的 b 与当前持有的 boi(都为boid) 的三维间距
dist = delta.magnitude; //实数间距 if (dist < closesDist)
{
closesDist = dist; //更新最小间距
closest = b; //更新最近的 boid 为 b
} if (dist < BoidSpawner.S.nearDist) //处在附近的 boid 范围
neighbors.Add(b); if (dist < BoidSpawner.S.collisionDist) //处在最近的 boid 范围(有碰撞风险)
collisionRisks.Add(b);
} if (neighbors.Count == 0) //若没有其他满足邻近范围的boid,则将自身boid纳入附近的boid表中
neighbors.Add(closest); return (neighbors);
} //获取 List<Boid>当中 所有Boid 的平均位置
public Vector3 GetAveragePosition(List<Boid> someBoids)
{
Vector3 sum = Vector3.zero;
foreach (Boid b in someBoids)
sum += b.transform.position;
Vector3 center = sum / someBoids.Count; return (center);
} //获取 List<Boid> 当中 所有Boid 的平均速度
public Vector3 GetAverageVelocity(List<Boid> someBoids)
{
Vector3 sum = Vector3.zero;
foreach (Boid b in someBoids)
sum += b.velocity;
Vector3 avg = sum / someBoids.Count; return (avg);
}
}

参考

《游戏设计、原型与开发》 - Jeremy Gibson

Unity项目 - Boids集群模拟算法的更多相关文章

  1. 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法

    "为什么这个功能用不了?" 程序员:"清一下缓存" 上篇洞悉系列文章给大家详细介绍了MySQL的存储内幕:洞悉MySQL底层架构:游走在缓冲与磁盘之间.既然聊过 ...

  2. elasticsearch介绍集群,模拟横向扩展节点、节点宕机、改变分片

        出处:[http://www.cnblogs.com/dennisit/p/4133131.html] ,防楼主删博,故保留一份! elasticsearch用于构建高可用和可扩展的系统.扩展 ...

  3. Eclipse中将hadoop项目放在集群中运行

    1.加入配置文件到项目源码目录下(src) <configuration> <property> <name>mapreduce.framework.name< ...

  4. 基于 SOA 架构,创建 ego-search-web 项目-solr集群-zookeeper集群

    项目架构 Ego-search-web 服务的消费者,ego-rpc 服务提供者 建立 ego-search-web 项目 继承:ego 依赖:ego-common   ego-rpc-service ...

  5. WEB项目会话集群的三种办法

    web集群时session同步的3种方法 在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上, 如果session不同步的 ...

  6. 高级项目 它 集群环境建设(两)MySQL簇

    最后博文我们介绍一下相关概念集群,今天我们要介绍的博文MySQL相关内容集群. 1.MySQL集群简单介绍 MySQL群集技术在分布式系统中为MySQL数据提供了冗余特性,增强了安全性,使得单个MyS ...

  7. Linux集群

    集群的起源: 集群并不是一个全新的概念,其实早在七十年代计算机厂商和研究机构就开始了对集群系统的研究和开发.由于主要用于科学工程计算,所以这些系统并不为大家所熟知.直到Linux集群的出现,集群的概念 ...

  8. 从数据仓库双集群系统模式探讨,看GaussDB(DWS)的容灾设计

    摘要:本文主要是探讨OLAP关系型数据库框架的数据仓库平台如何设计双集群系统,即增强系统高可用的保障水准,然后讨论一下GaussDB(DWS)的容灾应该如何设计. 当前社会.企业运行当中,大数据分析. ...

  9. apache + tomcat 集群

    apache2.2与tomcat集成(可以多个tomcat) 需求概况: 有3个服务: localhost:9091, localhost:9190. localhost:9191分别对应3个tomc ...

随机推荐

  1. 九度oj 题目1047:素数判定

    题目1047:素数判定 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:12466 解决:5644 题目描述: 给定一个数n,要求判断其是否为素数(0,1,负数都是非素数). 输入: 测试数 ...

  2. node框架express里面静态文件中间件express.static,根据路径名查找文件

    - 是express框架下的一个方法,可以根据请求路径名查找某个文件下文件名字和路径名相同的文件 - 3.X里面有20多个中间件,但是在4.X里面 只保留了express.static - 语法 ex ...

  3. SpringBoot Data JPA 关联表查询的方法

    SpringBoot Data JPA实现 一对多.多对一关联表查询 开发环境 IDEA 2017.1 Java1.8 SpringBoot 2.0 MySQL 5.X 功能需求 通过关联关系查询商店 ...

  4. codevs1128 导弹拦截

    题目描述 Description 经过11 年的韬光养晦,某国研发出了一种新的导弹拦截系统,凡是与它的距离不超过其工作半径的导弹都能够被它成功拦截.当工作半径为0 时,则能够拦截与它位置恰好相同的导弹 ...

  5. Gym100812 L 扩展欧几里得

    L. Knights without Fear and Reproach time limit per test 2.0 s memory limit per test 256 MB input st ...

  6. FTPUtil工具类

    package com.xxx.common.util; import java.io.File; import java.io.FileOutputStream; import java.io.IO ...

  7. Ubuntu 16.04修改显示字体大小(包括GNOME/Unity)

    在Ubuntu中字体都基本偏大,且和分辨率无关. Unity: 安装Unity Tweak Tool sudo apt-get install unity-tweak-tool GNOME: 打开优化 ...

  8. Ubuntu 16.04下搭建基于携程Apollo(阿波罗)配置中心单机模式

    官网:https://github.com/ctripcorp/apollo Wiki:https://github.com/ctripcorp/apollo/wiki(一切的集成方式和使用方法都在这 ...

  9. Orcle定时生成表数据作业

    --建表 create table table41( id ) not null, --主键 col1 ), col2 ), col3 ), col4 int, col5 timestamp, col ...

  10. Oracle SqlPlus导出查询结果

    Oracle SqlPlus导出查询结果 在sqlplus下导出查询的结果保存到本地sql文件中,可以采用如下方式:1.连接数据库: sqlplus xmq/xmqpwd@192.168.1.57:1 ...