Unity项目 - Boids集群模拟算法

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

原文地址:https://www.cnblogs.com/SouthBegonia/p/10975851.html

时间: 2024-10-13 06:59:11

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

steering behaviors 转向行为 集群模拟 小结

继今年(虽然离2015年只有一天了但还是2014)暑假简单接触了一些flocking 集群的概念后一直没有完完整整的把这个算法里面的一些行为规则做一些归类和实现.最近一直还在深入的学习Houdini里面的VEX语言,这里简单讲一讲我的一些学习过程,并附上在Houdini里面实现的方法和源码. 集群模拟中每个个体的行为规则(前 1 - 8 是已经实现了的,后面的在游戏领域拥有应用,这里只提一下). seek 寻找 flee 逃跑 wander 随意行走 separation 分离 cohesion

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

1.加入配置文件到项目源码目录下(src) <configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration> 读取配置文件内容,使项目知道向集群提交运行 2.将本项目打包到项目源码目录下(src) 3.在Java代码加入下一句话 Configurati

Redis3.0集群crc16算法php客户端实现方法(php取得redis3.0集群中redis数据所在的redis分区插槽,并根据分区插槽取得分区所在redis服务器地址)

数据分区        Redis集群将数据分区后存储在多个节点上,即不同的分区存储在不同的节点上,每个节点可以存储多个分区.每个分区在Redis中也被称为"hash slot",Redis集群中总共规划了16384个分区. 例如:当集群中有3个节点时,节点A将包含0-5460分区,节点B将包含5461-10922分区,节点C将包含10923-16383分区. 每个key将会存储到一个唯一的分区中,每个分区其实就是一组key的集合,两者对应关系为:key的CRC16校验码%16384=

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

最后博文我们介绍一下相关概念集群,今天我们要介绍的博文MySQL相关内容集群. 1.MySQL集群简单介绍 MySQL群集技术在分布式系统中为MySQL数据提供了冗余特性,增强了安全性,使得单个MySQLserver故障不会对系统产生巨大的负面效应,系统的稳定性得到保障. MySQL群集须要有一组计算机.每台计算机的角色可能是不一样的.MySQL群集中有三种节点:管理节点.数据节点和SQL节点.群集中的某计算机可能是某一种节点.也可能是两种或三种节点的集合.这三种节点仅仅是在逻辑上的划分,所以它

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

web集群时session同步的3种方法 在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上, 如果session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态.所以本文就根据这种情况给出三种不同的方法来解决这个问题: 一,利用数据库同步session 在做多服务器session同步时我没有用这种方法,如果非要用这种方法的话,我想过二种方法: 1,用一个低端电脑建个数据库专门存放web服务器的sessio

Nginx IP集群选择算法Java

查看Nginx 源码,将Nginx IP选择算法变为Java: package com.csst.msm; public class Test { /** * @param args */ public static void main(String[] args) { int hash =89;//初始值 int serverNum =2;//服务器个数 int addrlen=3;//IPV4 只计算前3个字节 int p; int[] addr = new int[]{192,168,10

Linux rhel7.0 pacemaker集群搭建和配置

一 集群环境介绍 一 Linux 集群发展史 高可用集群的层次结构1 消息/基础架构 corosync 2 成员关系 :监听心跳信息,并进行处理成员关系和计算成员关系的票数等信息3 资源管理 VIP 磁盘 文件系统 CRM (群集资源管理器)等,有些策略引擎(有些资源是放置在同一个节点和其依赖关系) 和资源的分配调度有关 4 资源 : 对特定资源的操作,通过一定的脚本实现pacemaker 群集资源管理器corosync 消息/基础架构 管理工具 crmsh : crm (cluster res

[转帖]当 K8s 集群达到万级规模,阿里巴巴如何解决系统各组件性能问题?

改天学习一下. https://www.cnblogs.com/alisystemsoftware/p/11570806.html 作者 | 阿里云容器平台高级技术专家 曾凡松(逐灵) 本文主要介绍阿里巴巴在大规模生产环境中落地 Kubernetes 的过程中,在集群规模上遇到的典型问题以及对应的解决方案,内容包含对 etcd.kube-apiserver.kube-controller 的若干性能及稳定性增强,这些关键的增强是阿里巴巴内部上万节点的 Kubernetes 集群能够平稳支撑 20

教你如何利用分布式的思想处理集群的参数配置信息——spring的configurer妙用

引言 最近LZ的技术博文数量直线下降,实在是非常抱歉,之前LZ曾信誓旦旦的说一定要把<深入理解计算机系统>写完,现在看来,LZ似乎是在打自己脸了.尽管LZ内心一直没放弃,但从现状来看,需要等LZ的PM做的比较稳定,时间慢慢空闲出来的时候才有机会看了.短时间内,还是要以解决实际问题为主,而不是增加自己其它方面的实力. 因此,本着解决实际问题的目的,LZ就研究出一种解决当下问题的方案,可能文章的标题看起来挺牛B的,其实LZ就是简单的利用了一下分布式的思想,以及spring框架的特性,解决了当下的参