本文會使用前端技術來模擬2d和3d鳥羣,我選用canvas元素繪製,固然也能夠使用css3或者svg。css
整個實現 demo前端
3d的渲染我選用threejscss3
2d的渲染引擎我選擇cax,cax是一款我很是喜歡的渲染引擎,支持小程序、小遊戲以及 Web 瀏覽器渲染。用它既能開發小遊戲也能開發圖表(見wechart),強力推薦!git
2d demogithub
這些鳥並非在漫無目的的亂飛,它們看上去都擁有了智商,造成了羣體,產生了複雜的羣組運動效果canvas
這看上去複雜的行爲是一種複合行爲,一般會拆分爲下面幾種小程序
這三個特性分離、內聚、排隊組合起來,就會獲得飛車逼真的鳥羣(羣體)瀏覽器
考慮到鳥羣的複雜行爲,咱們沒必要知道鳥羣的總體智慧。每隻鳥只須要對它範圍內的其餘鳥運用那三個特性,天然而然的造成羣落。來看下面的代碼,代碼沒作太多優化,將就着看:ide
建立鳥們,添加到場景中svg
var birds = Array.from({length:60},(v,i)=>{
var bird = new Bird()
stage.add(bird)
Object.assign(bird,{
po: new Vector(stage.width*rd(),stage.height*rd())
,ve: new Vector(rd() * 20 - 10, rd() * 20 - 10)
,ac: new Vector()
})
return bird
})
this.tick = cax.tick(()=>{
stage.update()
birds.forEach(b=>b.update(stage,birds))
})
複製代碼
關鍵update方法
update(birds){
// 忽略本身
birds = birds.filter(o=>this!=o)
// separation
for(let bird of birds){
// 過於接近
if(this.po.distanceTo(bird.po) < Bird.sDist){
// 減去這個轉向力,實現分離
this.ac.sub(
bird.po.clone()
.sub(this.po).normalize()
.multiplyScalar(Bird.MAX_SPEED)
.sub(this.ve)
)
}
}
// cohesion
let cohesion = birds.reduce((param, b)=>{
if(this.po.distanceTo(b.po) < Bird.cDist){
param.sum.add(b.po)
param.count++
}
return param
},{sum:new Vector(),count:0,force:new Vector()})
if(cohesion.count>0){
// 先求得平均位置,用平均位置求得指望的目標速度,把算出的轉向力累積到加速度上
this.ac.add(
cohesion.sum.divideScalar(cohesion.count)
.sub(this.po).normalize()
.multiplyScalar(Bird.MAX_SPEED)
.sub(this.ve)
)
}
// alignment
let alignment = birds.reduce((param, b)=>{
if(this.po.distanceTo(b.po) < Bird.aDist){
param.sum.add(b.ve)
param.count++
}
return param
},{sum:new Vector(),count:0,force:new Vector()})
if(alignment.count>0){
// 先求得平均速度,用平均速度求得指望的目標速度,把算出的轉向力累積到加速度上
this.ac.add(
alignment.sum.divideScalar(alignment.count).normalize()
.multiplyScalar(Bird.MAX_SPEED)
.sub(this.ve)
)
}
// 不超過最大轉向力和速度
if(this.ac.length > Bird.MAX_FORCE) this.ac.normalize().multiplyScalar(Bird.MAX_FORCE)
if(this.ve.length > Bird.MAX_SPEED) this.ve.normalize().multiplyScalar(Bird.MAX_SPEED)
// 讓質量大的鳥慢下來
this.ve.add(this.ac.divideScalar(this.mass))
this.po.add(this.ve)
// ...
}
複製代碼
就是先忽略到本身,若是搜尋了那些過於接近的鳥,則把計算出的轉向力累加到加速度上。注意的是,對於過於接近的判斷其實還有個附加條件,就是在視場內。上面的代碼並無加上這個條件,不過也能模擬的較好,我就沒寫=w=,鳥的視場一般是180度的,是否在180度內,知足的是 this.ve.dot(this.po.clone().sub(bird.po)) < 0
, 對視野和其餘數值進行調整,都會造成不同的羣落效果。
咱們還能夠疊加多層的信息生成更復雜的模擬。這裏的鳥都是一類鳥,能夠添加一個老鷹對象,若是小鳥和老鷹的距離超過了必定閾值小鳥就會立馬逃跑。要模擬這種狀況,只要再添加一種逃離的行爲到整個系統中,這種行爲還會致使小鳥的總轉向力,速度所有上升。
3d的實現和2d其實原理相似,惟一要注意的地方就是對象須要往目標方向的轉向問題,這一般會使用四元數來進行處理,代碼關鍵就是
this.rot.setFromQuaternion(
new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), new THREE.Vector3(this.ve.x, this.ve.y, this.ve.z).normalize())
)
this.rotation.copy(new THREE.Euler(this.rot.x,this.rot.y,this.rot.z))
複製代碼