TomLooman_ActionRoguelike_第十三章AI與框架擴展
該專欄用于保存對TomLooman的ActionRoguelike項目的學習筆記,學習過程中的思考與記錄不一定準確。
教程參考:https://github.com/tomlooman/ActionRoguelike
基于UE5.0的項目實現(xiàn):https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_08_12
敲定AI,擴展框架:bot的死亡和布娃娃效果,bot對受傷的反應,靜態(tài)函數(shù),改進bot的受傷邏輯
?
我們在GameMode中生成bot時有一個判斷,當世界中的bot過多時就再生成bot。之前我們把這個判斷放在環(huán)境查詢后,但是其實不需要生成bot時也就不需要進行環(huán)境查詢了,所以我們把這個判斷放到環(huán)境查詢前。
?
我們希望bot和玩家一樣在血量小于零后有死亡的效果,所以我們在AICharacter的OnHealthChanged成員函數(shù)中實現(xiàn)該功能。
這里判斷bot死亡用到的靜態(tài)函數(shù)下面會講,但該函數(shù)只是簡單地判斷bot血量是否小于零。當bot死亡后我們主要做三件事:(1)停止行為樹的運行,(2)啟用bot的布娃娃效果,模擬死亡動畫,(3)設置bot剩余生命時間。
停止行為樹運行要通過控制bot的AIController完成。GetBrainComponent
獲得AIController中負責行為的BrainComponent,它的StopLogic成員函數(shù)能夠停止正在運行的邏輯,StopLogic的參數(shù)不影響結果,只是對為什么停止行為的comment。
bot的布娃娃效果要通過SetAllBodiesSimulatePhysics啟用所有骨骼Mesh的物理模擬,且將碰撞預設從CharacterMesh改變?yōu)?#34;Ragdoll",否則bot會直接穿過地板(見下面這兩種碰撞的區(qū)別)。除了這種方法,使用動畫藍圖也是可以的,就像我們處理玩家角色的死亡那樣。
通過SetLifeSpan,我們可以改變Actor的剩余生命時間,時間耗盡后,該對象會被銷毀。



另外,我們模仿玩家角色死亡后的處理,取消了膠囊體碰撞,防止尸體吞子彈,但是這樣bot的尸體和玩家也沒有碰撞了,可能會有點奇怪。

?
?
我們使用的bot的物理資產(chǎn)如下,它有一個個膠囊體和立方體處理碰撞,而不是用三角形。

到目前為止,我們的bot對玩家攻擊是沒有反饋的,我們希望當玩家攻擊到bot時,bot能將該玩家視為TargetActor,從而進行后續(xù)的反擊?!爱斒艿焦魰r將對方設置為TargetActor”這一邏輯自然地可以寫在bot的OnHealthChanged中,因為OnHealthChanged的參數(shù)中就有觸發(fā)屬性組件中OnHealthChanged委托的Actor的指針。

因為在多處涉及到了訪問bot的AIController的行為樹的黑板,并對其中的TargetActor進行賦值的操作,所以我們將這一邏輯抽象為一個函數(shù)。

但是回想一下,目前為止,在屬性組件類中負責廣播OnHealthChanged委托的ApplyHealthChange中,OnHealthChanged.Broadcast的第一個參數(shù),也就是InstigatorActor,仍然是nullptr。所以我們需要進行修改,傳入真實的InstigatorActor,這樣也就要求調用ApplyHealthChange需要多傳入一個參數(shù)。

相應的,一些需要調用ApplyHealthChange的類,比如血包和子彈,都需要修改調用形式,血包和子彈的調用方式如下(血包其實與TargetActor無關,但為了形式統(tǒng)一還是加上了)。其中子彈類比較特殊,因為當bot被子彈攻擊時,它不應該把子彈當做TargetActor,而是應該把子彈的Instigator當做TargetActor。


需要注意的是,這里bot攻擊時并不檢查TargetActor是否是友軍,或者將某個Actor視為TargetActor時不檢查該Actor是否是友軍,所以可能會出現(xiàn)bot之間相互攻擊的情況。
?
有一些重復使用的簡單功能需要很長的代碼去實現(xiàn),比如獲取屬性組件成員變量,此時我們可以將這個功能抽象為一個靜態(tài)函數(shù)。
我們在屬性組件類中創(chuàng)建一個獲得Actor對象的屬性組件類成員函數(shù)的靜態(tài)函數(shù)如下,函數(shù)的實現(xiàn)和子彈類或者醫(yī)療包類完全相同。


這樣我們在想獲得一個Actor的屬性組件類成員變量時,就不需要先GetComponentByClass再Cast<USAttributeComponent>。

除此之外我們還在屬性組件類中定義了一個靜態(tài)函數(shù)判斷Actor是否存活,其中的說明符meta = (DisplayName = "IsAlive")可以指定該函數(shù)在藍圖中顯示的函數(shù)名稱。需要注意的是,在IsActorAlive或者類似的函數(shù)中,對默認返回值的設置要格外小心。


?
現(xiàn)在的bot并不知道玩家是否死亡,如果玩家角色死亡倒下了,我們會發(fā)現(xiàn)bot仍然在攻擊玩家角色,這是因為bot的TargetActor仍然是玩家角色,所以仍然會執(zhí)行攻擊邏輯。我們可以根據(jù)TargetActor是否存活,判斷是否中斷bot的攻擊。我們在行為樹的遠程攻擊節(jié)點中實現(xiàn)這個功能。
我們給bot的遠程攻擊加了一個判斷,除了判斷TargetActor是否有效外,還判斷TargetActor是否活著,這里就用到了上面定義的靜態(tài)函數(shù)。

理論上來說樹的一個任務節(jié)點失敗后,整棵樹會重新運行,那么我們就需要清除上一輪中TargetActor這樣的變量。但是這個操作并不歸遠程攻擊任務節(jié)點管,所以我們不在這里實現(xiàn)。
?
現(xiàn)在bot對我們的攻擊太精準的,我們希望能給bot的攻擊加一些偏移,就像真人那樣。我們依然在bot行為樹的遠程攻擊節(jié)點中實現(xiàn)這個功能。
之前我們獲得了bot的子彈生成旋轉,我們可以在這個旋轉上通過FMath::RandRange增加一些隨機數(shù)來實現(xiàn)偏移的效果。隨機數(shù)范圍的上下限是我們創(chuàng)建的成員變量,在構造函數(shù)中初始化,并暴露在藍圖中。
這里我們并沒有指定隨機數(shù)種子。但隨機數(shù)可以視為一個由隨機數(shù)種子決定的函數(shù),函數(shù)的自變量可以是游戲時間,應變量就是我們想要的隨機數(shù)。

