AI在UE4的示例里面主要用來做尋路、避障噪馏、探索等一些功能,在UE4的動畫制作中绿饵,同樣可以為角色設(shè)定一些AI動作欠肾,比如說對場景中一些物體的反應(yīng)、聽到不同對話聲音的一些反應(yīng)拟赊、空閑時隨機做一些自由動作等刺桃。
AI的循環(huán)處理主要分為三個階段:感知(Sense)、思考(Think)吸祟、行動(Act)虏肾。感知表示對AI當(dāng)前狀態(tài)進行記錄廓啊,包括物理感覺(聽覺視覺觸覺等),可以對這些物理感覺定制優(yōu)先級封豪,還能添加一些抽象的感知谴轮;思考表示AI利用感知到的信息,和目標進行一起評估吹埠,然后為下步行動指明方向第步;行動是根據(jù)感知和思考階段的信息,做出相應(yīng)的行為缘琅,如跑步粘都、跳躍、和其他AI通信等刷袍,當(dāng)行動結(jié)束后翩隧,又回到感知的狀態(tài)。
AI場景設(shè)計的參考流程如下:
1呻纹、新建一個CharacterBlueprint(角色藍圖)堆生,用于表示AI角色,并命名為AI_ThirdPersonCharacter雷酪;
2淑仆、新建一個AIController(AI控制器),并命名為AIC_ThirdpersonCharacter;
3哥力、打開AI的角色藍圖蔗怠,找到Pawn->AIControllerClass,選中之前創(chuàng)建的AI控制器,此處的AutoPossessPlayer是關(guān)閉的吩跋,表明控制方式是AI控制寞射;
4、將AI角色藍圖放入到Level場景中锌钮;
5桥温、使用AI尋路功能,在Level場景中添加“NavMeshBoneVolume”(尋路網(wǎng)格體)轧粟,此網(wǎng)格體功能就是預(yù)先計算好尋路數(shù)據(jù)(默認是按照靜態(tài)物體計算策治,如果要對移動的物體進行監(jiān)測,要在設(shè)置里打開動態(tài)監(jiān)測兰吟,會增加一定的計算機開銷)通惫;
選中Level場景中的NavMeshBoneVolume,在Details->BrushSetting中將Mesh進行放大混蔼,覆蓋整個地圖(鍵盤的P鍵是打開/關(guān)閉顯示導(dǎo)航路徑履腋,默認導(dǎo)航路徑是自動更新的);
在Level場景中放入NavMeshBoneVolume后,會自動生成一個RecastNavMesh-Default對象遵湖,用于輔助尋路網(wǎng)格工作悔政,如設(shè)置代理半徑/高度(AgentRadius/Height)等,這樣可以根據(jù)AI角色的實際大小延旧,設(shè)置更為精確的導(dǎo)航路徑谋国,同時Agent的代理數(shù)量可以根據(jù)需求自行添加;
6迁沫、UE4的預(yù)置模塊里芦瘾,還有NavModifierVolume,它的可以設(shè)置一些AI尋路的權(quán)重集畅,告訴AI哪些區(qū)域可以走近弟,哪些區(qū)域比較難走,示例中不做過多介紹挺智;
7祷愉、在藍圖中讓AI角色自由行走的方法(此方法相對比較簡單,處理的邏輯也不復(fù)雜赦颇,如果要使用復(fù)雜邏輯二鳄,后面會介紹使用行為樹BehaviorTree的方法):
打開AI角色(AI_ThirdpersonCharacter)->EventGraph;
新建自定義事件并命名為“RandomWalk”沐扳,調(diào)用函數(shù)“SimpleMoveToLocation”泥从,此函數(shù)中帶兩個輸入?yún)?shù)Controller對象和Goal位置句占,獲取AI角色藍圖里的Controller對象沪摄,通過調(diào)用Get Controller函數(shù),它會自動返回一個Pawn對象纱烘,之前我們在Pawn里用的AIControllerClass就是我們自建的AIC_ThirdpersonCharacter;
調(diào)用GetRandomMoveToLocation函數(shù)杨拐,這個函數(shù)會返回一個隨機的位置信息給SimpleMoveToLocation的Goal輸入,此函數(shù)輸入?yún)?shù)中的Origin表示初始點的位置信息擂啥,可以通過調(diào)用Get Actor Location獲取AI角色的當(dāng)前位置哄陶,Radius表示隨機去的半徑范圍;
調(diào)用系統(tǒng)事件Event Begin Play哺壶,此事件在AI角色藍圖運行時自動觸發(fā)屋吨,之后我們通過Set Timer by Event定時器函數(shù),自動去觸發(fā)之前設(shè)置的自定義事件Random Walk山宾,注意在定時器函數(shù)中至扰,把Loopping打開;
把所有環(huán)節(jié)串接起來资锰,編譯運行敢课,AI角色就會在Level場景中隨機移動,并且每隔固定的事件,重新尋找新的路徑直秆;
8濒募、GamePlay調(diào)試器的使用,可以用鍵盤上的撇號調(diào)出調(diào)試器圾结,調(diào)試器類似于編程里的Debugger瑰剃,可以設(shè)置斷點,可以查看Level場景中的各類參數(shù)筝野;
9培他、AI感知系統(tǒng)中有兩個重要的組成部分:AI感知組件(AIPerception)和AI感知刺激源(AIPerceptionStimuliSource);系統(tǒng)自帶事件OnTargetPerceptionUpdated遗座,當(dāng)感知發(fā)生變化時會自動觸發(fā)舀凛,并返回一個刺激源(Stimulus)和感知對象(Actor),感知的框圖如下:
10途蒋、在AI角色中(AI_ThirdpersonCharacter)中猛遍,添加感知組件(AIPerception),選中添加好的感知組件号坡,在Details->AIPerception->SenseConfig中懊烤,添加感知源(AISightConfig視覺感知),在視覺感知源中宽堆,還能詳細的設(shè)置視覺半徑和視覺范圍腌紧,在DetectionByAtilation中,勾選Neural畜隶,表示感知的監(jiān)測對象為中立方(一般玩家角色設(shè)置為中立方才能被監(jiān)測到)壁肋;
11、將玩家角色(ThirdPersonCharacter)拖入到Level場景中籽慢,在藍圖中添加刺激源組件(AIPerceptionStimuli Source)浸遗,選中刺激源,在Details->AIPerception箱亿,打開AutoRegisterSource跛锌,選擇RegisterAsSourceForSense->AI_Sense_Sight,表示將刺激源放到玩家角色上届惋,同時刺激源是視覺刺激髓帽;
默認ThirdPersonCharacter的類中自帶SightStimuli,計算不加刺激源脑豹,它也能被AI感知到郑藏,此處加上只是為了說明原理;
12晨缴、在AI_Third personCharacter中译秦,關(guān)閉Event Begin Play事件,不調(diào)用之前寫的簡單自動尋路藍圖,之后會用行為樹BehaviorTree的方式讓AI自動尋路筑悴;
13们拙、在AI_Third personCharacter中,添加AI感知事件OnTargetPerceptionUpdated阁吝,捕獲AI感知信息和對象砚婆,將感知到的對象存為內(nèi)部變量并命名為“PerceivedActor”,將捕獲到的刺激源突勇,通過BreakAIStimulus函數(shù)打碎装盯,這樣可以返回感知到的所有內(nèi)容和狀態(tài),這次例子中只用到了感知狀態(tài)的真假SuccessfullySensed甲馋,然后把這個Bool值傳遞給Branch分支函數(shù)埂奈,分支的真表示AI感知到玩家,感知到玩家后回去追蹤定躏,分支的假表示丟失玩家账磺,丟失后AI又會去自動尋路;
新建一個Target Lost的自定義事件痊远;
在分支假后面垮抗,設(shè)定一個事件觸發(fā)的計時器函數(shù)SetTimerByEvent,連接自定義事件Target Lost碧聪,并設(shè)定時間冒版,表示分支進入假狀態(tài)后,定時多少秒逞姿,自動去觸發(fā)Target Lost事件辞嗡;
在分支真后面,需要設(shè)定一個函數(shù)ClearAndInvalidateTimerByHandler哼凯,表示進入真以后欲间,自動清除之前分支假里面的SetTimerByEvent函數(shù)楚里,如果不清楚断部,可能會遇到Target被監(jiān)測到,仍舊會被分支假里的計時器清空的情況班缎,如果玩家一會進入AI感知范圍蝴光,一會又出去的情況;
AI_Third personCharacter的藍圖中达址,我們只是定義的感知后的處理邏輯蔑祟,具體怎么去處理,需要通過接下來的行為樹來實現(xiàn)沉唠;
14疆虚、行為樹(BehaviorTree)是將AI場景中的決策制定模型進行可視化操作的方法,相當(dāng)于思考這個環(huán)節(jié);行為樹的執(zhí)行順序為從上到下径簿,從左到右罢屈;
行為樹的根節(jié)點(Root)下方有各種節(jié)點,其中任務(wù)節(jié)點(Task)是行為樹分支的重點篇亭,有預(yù)置的任務(wù)(如Move to,Wait)缠捌,也可以自定義任務(wù);
選擇合成器(Selector)節(jié)點會執(zhí)行它下面的子節(jié)點译蒂,只要有子節(jié)點返回時True,那么Selector為True曼月,會繼續(xù)執(zhí)行,只有當(dāng)所有的子節(jié)點返回是False時柔昼,Selector才返回為False并退出哑芹;因此在Selector下的子節(jié)點,我們一般將優(yōu)先級最高的放左邊捕透;類似于去上班的任務(wù)绩衷,不管是走路還是坐地鐵還是公交車,最終我們都能到達公司激率;
Sequence合成器節(jié)點咳燕,它會遍歷下方所有子節(jié)點,當(dāng)都返回True時乒躺,它才返回True招盲,這個節(jié)點的方式與人們處理障礙的方式類似;
同時行為樹中還能定制一些服務(wù)(Service)和裝飾器(Decarotar)嘉冒,放置到Selector和Sequence節(jié)點中曹货,讓處理邏輯更加靈活;
15讳推、新建一個行為樹顶籽,并命名為BT_EnemyAI;
16银觅、新家一個黑板(BlackBoard)礼饱,并命名為BB_EnemyAI,關(guān)聯(lián)BT——EnemyAI究驴,黑板的作用是為行為樹提供參數(shù)和狀態(tài)镊绪,幫助行為樹來進行判定;
17洒忧、打開AI控制器AIC_ThiredpersonCharacter蝴韭,在EventGraph中,使用系統(tǒng)自帶事件EventOnPossess(表示AI控制器被執(zhí)行的時候觸發(fā))熙侍,然后調(diào)用RunBehaviorTree函數(shù)榄鉴,選擇之前創(chuàng)建的行為樹并執(zhí)行行為樹履磨;
18、在黑板(BB_EnemyAI)中新建一個Key庆尘,命名為Target Location蹬耘,類型設(shè)置為Vector,我們后面其實就是要把AI角色藍圖里的數(shù)據(jù)减余,傳遞給黑板综苔,黑板再告知行為樹進行判定;
19位岔、在行為樹中使用自動尋路Task的方法如下:
在行為樹中如筛,新建一個自動尋路的任務(wù)(Task),來用作自動尋路抒抬,并命名為“BTT_FindNavigationLocation”杨刨,之前再AI角色藍圖中(AI_ThirdpersonCharacter)中已經(jīng)去掉的自動尋路事件觸發(fā),因此在AI角色藍圖中擦剑,不會執(zhí)行自動尋路妖胀,我們把自動尋路的功能改成的Task的方式,供行為樹使用惠勒;
進入BTT_FindNavigationLocation任務(wù)赚抡,添加系統(tǒng)事件ReceiveExecuteAI,當(dāng)行為樹執(zhí)行該任務(wù)時纠屋,會自動觸發(fā)此事件涂臣,ReceiveExecuteAI事件執(zhí)行后返回兩個對象Owener Controller和ControlledPawn,我們可以通過將Owener Controller對象輸出給GetActorLocation函數(shù)售担,來返回一個AI對象的位置赁遗;
把這個AI對象的位置傳遞給Get Random Reachable PointInRadius函數(shù),并輸出一個隨機位置信息族铆;
將隨機的位置信息通過SetBlackBoardValueAsVector函數(shù)傳遞給BlackBoard岩四,SetBlackBoardValueAsVector中還有一個Key的輸入變量,我們在Task中新建一個BlackBoardKey類型的變量哥攘,并設(shè)置成公有變量命名為“Target Location Key”,將此共有變量傳遞給SetBlackBoardValueAsVector剖煌;之后只要在行為樹的黑板中,把這個Key對應(yīng)上献丑,就能準確傳遞隨機位置末捣;
在Task任務(wù)的最后,加上FinishExecute创橄,并勾選success,或者將之前Get Random Reachable PointInRadius的狀態(tài)發(fā)給它莽红;所有Task任務(wù)中妥畏,一定要加上FinishExecute邦邦,表示任務(wù)已經(jīng)完成,不然在行為樹中醉蚁,不會去執(zhí)行接下來的任務(wù)燃辖;
20、注意Task任務(wù)如果失敗网棍,那么它的父節(jié)點Sequence也會失敗并終止黔龟;將BTT_FindNavigationLocation任務(wù)加入到行為樹中,此時公共變量Target Location Key暴露給了行為樹滥玷,將此變量綁定到黑板中的Target Location啊氏身,這樣就能將Key對應(yīng)的隨機位置數(shù)值,傳遞給黑板惑畴,具體行為樹中流程圖如下:
21蛋欣、在行為樹中設(shè)置追蹤玩家角色的邏輯,在黑板中(BB_EnemyAI)中如贷,新建一個Key命名為TargetActor陷虎,類型為Actor,新建一個Key杠袱,命名為HasLineOfSight尚猿,類型為Bool;
22楣富、打開AI控制器(AIC_ThirdpersonCharacter)谊路,新建兩個函數(shù)UpdateTargetActorKey和UpdateHasLineOfSightKey,這兩個函數(shù)之后會在AI角色藍圖中被調(diào)用菩彬,它們的主要作用是缠劝,把AI角色藍圖中感知到的對象以及感知狀態(tài),發(fā)送給黑板骗灶,這兩個函數(shù)里面主要調(diào)用的一個函數(shù)是SetValueAsObject,SetValueAsObject函數(shù)的輸入包含Value惨恭、Target和Key,其中Target對象通過通過GetBlackboard來直接獲取耙旦,Key的值通過自定義脱羡,并跟實際行為樹中的黑板值對的上,Value值就是AI角色藍圖中送過來的免都;
23锉罐、下圖列出了整個AI搭建中的框圖,供參考和理解:
24绕娘、在行為樹(BT_EnemyAI)中創(chuàng)建一個Sequence用于追蹤玩家并命名為ChasePlayer脓规,并把這個Sequence放到之前隨機走動Sequence的左側(cè),子節(jié)點使用自帶的任務(wù)MoveTo和Wait侨舆,其中Move指向之前BB_EnemyAI中建立的Key-TargetActor秒紧;
25、在ChasePlayer的Sequence上添加裝飾器(Decarotar)挨下,用于判定目標對象是否被鎖定熔恢;添加完后選擇裝飾器,在Details->BlackBoard->KeyQuery(選擇is Set)->BlackBoardKey(選擇TargetActor)臭笆;在Details->FlowControl(流控制)->NotifyObserver(通知觀察者)->OnResualt->ObserverAborts(選擇Both);
此處的設(shè)定表示AI在隨機行走時叙淌,如果發(fā)現(xiàn)目標,則會停止隨機行走愁铺,轉(zhuǎn)而追蹤目標鹰霍;如果在追蹤目標時,目標丟失后帜讲,當(dāng)目標狀態(tài)發(fā)生變化衅谷,AI會立即終止追蹤,改為隨機行走似将;
26获黔、使用EQS環(huán)境查詢(首先需要在編輯配置中將其打開),操作方法如下:
新建EQS查詢文件EnvironmentQuery在验,命名為EQS_FindClosestFoodSource,雙擊進入編輯頁面玷氏,拖動其根節(jié)點(Root),并在生成器里選擇生成ActorOfClass(之前需要建一個FoodSource腋舌,模擬食物盏触,取消碰撞機制,然后把這些Actor托到Level場景中)块饺,取消GenerateOnlyActorInRadius(此處表示場地小赞辩,AI完全可以在場地里尋找,不需要設(shè)置額外的尋找食物半徑)授艰;
右擊ActorOfClass生成器辨嗽,添加一個條件-DistanceToQuery,在對應(yīng)的Details中淮腾,將TestPurpose設(shè)置為ScoreOnly糟需,然后在ScoreFactor中設(shè)置因子為-1;(這樣表示偏好距離更短的選擇谷朝,也就是說距離越短洲押,權(quán)重越大)
在BlackBoard中(BB_EnemyAI),新建一個Key圆凰,類型選擇Food Source(之前建的Actor)杈帐,并命名為TargetFoodSource;
回到行為樹中(BT_EnemyAI),在Selector下方新建一個Sequence送朱,放在追蹤和隨機行走的Sequence中間娘荡,并命名為FindFood干旁;
在FindFood的Sequence下驶沼,新建EQS查詢?nèi)蝿?wù)炮沐,選擇EQS查詢,在Details->EQSRequest->選擇之前建立的EQS文件回怜;在RunMode中大年,選擇SingleBestItem(即最優(yōu)的選項);在BlackBoard->BlackBoardKey中玉雾,選擇要查詢的對象FoodSource翔试;
在FindFood的Sequence下,EQS查詢?nèi)蝿?wù)的旁邊复旬,新建Move To任務(wù)垦缅,選擇對象時Target Food Source,再建立Wait任務(wù)驹碍,設(shè)置等待時間壁涎;
根據(jù)饑餓值來判斷是否是找食物,還是自由行走志秃,還是追蹤玩家(默認追蹤玩家的優(yōu)先級最高怔球,其次是找食物,最后是自由行走)浮还;
在黑板中(BB_EnemyAI)竟坛,新建一個Float類型的Key,并命名為Hunger钧舌;
在行為樹中(BT_EnemyAI)担汤,新建一個Service服務(wù),命名為BTS_Hunger洼冻,打開Service文件崭歧,在其內(nèi)部新建一個公共變量,命名為HungerKey,類型是BlackBoardKey碘赖;
在Service中添加一個系統(tǒng)事件Event Receive Tick AI(表示每次服務(wù)更新時會觸發(fā)驾荣,服務(wù)和任務(wù)的區(qū)別在于,服務(wù)會一直循環(huán)運行普泡,而Task運行完會結(jié)束播掷,下次要使用需要重新調(diào)用);
在Service中撼班,內(nèi)部新建一個公共變量歧匈,命名為HungerIncreaseRate,類型是Float砰嘁;每次服務(wù)更新時件炉,通過HungerIncreaseRate增加饑餓值勘究,并把饑餓值傳遞給黑板(BB_EnemyAI,通過函數(shù)SetBlackBoardValueAsFloat實現(xiàn))斟冕;
將Service服務(wù)放進行為樹的Selector中口糕,讓它一直運行,然后將HunggerKey綁定到黑板中的Hunger磕蛇;在Selector中景描,選擇Service后,還能調(diào)整Service的更新時間間隔秀撇,以及隨機偏差(RandomDerivative超棺,此處我們不加偏差);
在FindFood的Sequence中呵燕,添加一個裝飾器棠绘,類型是BlackBoard,在裝飾器的Details->BlackBoardKey再扭,選擇Hunger氧苍,并設(shè)置Key的值是1,此處表示當(dāng)Key值到1時霍衫,裝飾器會運行FindFood序列候引;
再建立一個任務(wù),命名為BTT_SetKeyFloat敦跌,其目的是當(dāng)Hunger值到1時澄干,自動清零;
打開BTT_SetKeyFloat任務(wù)柠傍,在里面新建一個Float Key公共變量麸俘,類型是BlackBoard Key,再新建一個FloatValue公共變量惧笛,類型是Float从媚,設(shè)置其默認值是0,使用SetBlackBoardValueAsFloat設(shè)置黑板上的Hunger值患整;
最后將BTT_SetKeyFloat任務(wù)放到FindFood的Sequence下拜效,并放到最后;