1987年Craig W.Reynolds發表一篇名爲《鳥羣、牧羣、魚羣:分佈式行爲模式》的論文,描述了一種很是簡單的、以面向對象思惟模擬羣體類行爲的方法,稱之爲 Boids ,Boids 採用了三個核心的規則:git
由此咱們採用Unity來實現算法並演示,演示結果:
github
每一個boid對象,每幀都有2個關鍵的表:與該boid鄰近的boids的表及與該boid最近的boids的表。根據兩個表求得一些肯定該boid位置、方向、速度的因素(例如其餘boids的平均速度,其餘boids的平均位置,該boid與其餘boid的平均距離等),根據所提出的三規則,設置各影響因素的權重比例,最終全部影響因素加和成爲肯定的、該boid下一幀的方向、位置、速度。算法
BoidSpawner.cs
綁定於主相機Boid.cs
綁定於預製體Boid上boidPrefab
變量爲預製體 boid項目地址:Boidsdom
/*-------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分佈式