基礎(chǔ) | 自治智能體----類鳥群(三)

本系列為筆者初學(xué)c/c++和游戲AI開發(fā)的學(xué)習(xí)過程,算法為主,不涉及到具體的游戲開發(fā)軟件學(xué)習(xí)(如unity,虛幻4等),若有錯(cuò)誤請(qǐng)?jiān)谠u(píng)論區(qū)留下批評(píng)意見。
語言:c/c++ (11及以上)
平臺(tái):macOS mojave
編譯器:vs Code / g++

二、一群亂飛的鳥
2.1 一只亂飛的鳥
? 我們知道一只現(xiàn)實(shí)中的鳥,會(huì)在遇到天敵的時(shí)候加速飛開,或者藏到鳥群中,也會(huì)四處盤旋以尋找果腹的食物。這也是我們的程序最后要實(shí)現(xiàn)的功能,但如此多復(fù)雜的功能,要一步實(shí)現(xiàn)顯然是非常困難的。
? 所以在那之前,我們后退一大步,先實(shí)現(xiàn)一個(gè)簡(jiǎn)單的小目標(biāo)----創(chuàng)建一只亂飛的鳥,這樣實(shí)現(xiàn)起來就容易多了。之后,我們?cè)诼慕虝?huì)這只“笨鳥”更多實(shí)用的“技能”。
? 首先,我們要將鳥抽象成計(jì)算機(jī)可以理解的對(duì)象,這將由一個(gè)類來實(shí)現(xiàn)。暫時(shí)先拋開鳥的大小、外形等因素,要抽象出空間中的一只鳥,至少需要三個(gè)屬性:
location:位置向量,描述一只鳥在平面空間中的位置
velocity:速度矢量,描述一只鳥的當(dāng)前飛行速度
acceleraton:加速度矢量,描述一只鳥的當(dāng)前加速度
? 有了以上三個(gè)屬性,我們就可以利用加速運(yùn)動(dòng)公式計(jì)算出一只鳥的空間位置和運(yùn)動(dòng)路徑。除此之外,我們?cè)?a target="_blank" >第一章節(jié)中學(xué)到,類鳥群Boids可歸納出三種主要的行為模式:
separation: 領(lǐng)域內(nèi)若有其他boids存在,該函數(shù)計(jì)算出一個(gè)距離,使當(dāng)前boid與其他boids保持距離。
alignment: 該函數(shù)計(jì)算出一個(gè)速度矢量,使其與其他boids保持相同的速度,并維持隊(duì)列。
cohesion: 計(jì)算出附近boids的質(zhì)心,并使當(dāng)前boid前往該質(zhì)心。
? 這三種主要的行為模式,是由一些次要的操控行為(Steering Behaviors)組成:
seek:靠近,控制boid靠近一個(gè)目標(biāo)
flee:離開,控制boid離開一個(gè)目標(biāo)
pursuit:追逐,控制“獵食者”追捕一個(gè)目標(biāo),該行為只有“獵食者”才擁有
evade:逃避,控制boid逃離一個(gè)目標(biāo),功能與flee類似,但其作用主要用來躲避“獵食者”的追捕,因此需要額外的功能
wander:徘徊,當(dāng)環(huán)境安全時(shí),一只鳥會(huì)在世界里游蕩嬉戲,隨機(jī)運(yùn)動(dòng)
obstacle avoidance:避開障礙,當(dāng)遇到障礙物的時(shí)候,繞開障礙物
? 以上方法便可以創(chuàng)造出一群足夠智能的機(jī)器鳥,當(dāng)然我們不會(huì)一下子就全都實(shí)現(xiàn)。在實(shí)現(xiàn)一只足夠智能的鳥之前,我們先搞出一只只會(huì)徘徊亂飛的“笨鳥”,如圖9:

2.2 Vector2D
? 在講解具體的代碼實(shí)現(xiàn)之前,我們有必要了解一下一個(gè)十分重要的組件Vector2D類。
? 簡(jiǎn)單來講,該類存儲(chǔ)智能體的位置信息(x和y),并提供必要的向量運(yùn)算工具,項(xiàng)目中涉及到向量運(yùn)算的地方,全都由該工具類來實(shí)現(xiàn)。
? 該工具類具體的實(shí)現(xiàn)和用法,請(qǐng)各位小伙伴下載代碼查看注釋即可。

2.2 seek靠近
? seek方法是最基礎(chǔ)的方法,該函數(shù)輸入一個(gè)目標(biāo)位置向量,并返回一個(gè)操控智能體到達(dá)目標(biāo)位置的力(表現(xiàn)為加速度a)。
? 我們知道力是一個(gè)向量,由大小和方向組成,因此只要得到了智能體的受力,就可以很容易的計(jì)算出其運(yùn)動(dòng)軌跡來。如圖10:

計(jì)算預(yù)期的速度,該速度是從智能體到目標(biāo)的向量,大小為智能體的最大速度。只需將目標(biāo)位置向量減去智能體位置向量,再將其標(biāo)準(zhǔn)化,就能得到該速度的方向。之后再乘以智能體最大速度,就能計(jì)算出最終的速度向量。
預(yù)期速度 = ||(目標(biāo)位置 - 智能體位置)|| * 最高速度
計(jì)算智能體受力,即智能體在當(dāng)前速度和預(yù)期速度的影響下,下一時(shí)刻的受力情況,也就是運(yùn)動(dòng)軌跡。
智能體受力 = 預(yù)期速度 - 當(dāng)前速度
? 我們首先計(jì)算預(yù)期速度(藍(lán)色箭頭),再將預(yù)期速度減去當(dāng)前速度,就可以得到智能體受力(紅色箭頭)。紅色箭頭即為我們所需的受力。代碼如圖11:

2.3 wander徘徊
??wander方法產(chǎn)生一個(gè)操控力,驅(qū)使智能體在環(huán)境中隨機(jī)走動(dòng)。
? 我們采用Reynolds的解決方案,通過一個(gè)圓來計(jì)算智能體的運(yùn)動(dòng)路徑。
? 在智能體周圍畫一個(gè)圓圈,圓心與智能體位置相同。接著,在圓圈上選一個(gè)目標(biāo)位置,讓目標(biāo)產(chǎn)生一個(gè)隨機(jī)抖動(dòng)值,使其離開圓圈。然后,將偏離圓圈的目標(biāo)映射回圓圈上,把目標(biāo)連同圓一起向右移動(dòng)一定距離。最后,智能體朝新的目標(biāo)位置移動(dòng)。

? 具體步驟如下:
對(duì)目標(biāo)增加一個(gè)隨機(jī)位移wanderJitter,使其偏離圓圈。這里在具體實(shí)現(xiàn)的時(shí)候,要算上時(shí)間差值,使運(yùn)動(dòng)更平滑。這里,wanderTarget目標(biāo)要先初始化,即先把圓圈畫出來。

將偏移后的目標(biāo),重新投影到圓圈上。計(jì)算公式:新目標(biāo) = 舊目標(biāo)單位向量 * 圓半徑

向右移動(dòng)目標(biāo)的位置,距離為wanderDistance,使其遠(yuǎn)離智能體。

接著,計(jì)算出此時(shí)目標(biāo)的位置向量,將其投影到世界空間中,即智能體運(yùn)動(dòng)的環(huán)境。
最后,通過seek方法計(jì)算出智能體到目標(biāo)的受力,然后通過速度運(yùn)動(dòng)公式就可以計(jì)算出智能體到目標(biāo)的運(yùn)動(dòng)路徑。


2.4 增量時(shí)間
??由于篇幅限制,一些非核心部分的工程實(shí)現(xiàn)就不多細(xì)講,但有一個(gè)地方需要特別注意。
? 實(shí)際動(dòng)手用Unity3D或UE做過游戲的小伙伴,一定記得游戲制作過程中,控制物體運(yùn)動(dòng)時(shí),需要給速度添加一個(gè)增量時(shí)間?deltaTime來使物體的運(yùn)動(dòng)更加的平滑。
? 在這個(gè)項(xiàng)目中,我們一樣也需要考慮到這個(gè)因素。在這里,我們使用SFML系統(tǒng)模塊中自帶的時(shí)鐘函數(shù)來實(shí)現(xiàn)該功能。
? 代碼中的elapsed變量就是增量時(shí)間,我們需要在窗口每一幀渲染的時(shí)候,計(jì)算出該變量的值,然后傳遞給每個(gè)智能體。

2.5 結(jié)果演示


參考:
《游戲人工智能編程案例精粹》
相關(guān)代碼下載? ??https://github.com/linpeijie/GameToy