Unreal Engine 4 系列教程 Part 9:AI教程


原文:Unreal Engine 4 Tutorial: Artificial Intelligence
作者:Tommy Tran
譯者:Shuchang Liu

在本篇教程中,你將學習如何使用行為樹和AI感知來創建一個能四處走動,攻擊敵人的簡單AI。

在視頻游戲中,人工智能(AI)通常指的是擁有自主決策行為的非玩家角色。AI可以是看到玩家然后進行攻擊的簡單角色,也可以是即時策略(RTS)游戲里的強大對手。

在Unreal引擎里,我們可以通過行為樹創建AI。行為樹是一個決定AI做哪種行為的實時決策系統。比如,如果AI有戰斗和逃跑兩種行為。你可以創建行為樹,讓AI在高於50%血量時進行戰斗,低於50%血量時逃跑。

在本篇教程中,你將學習到:

  • 創建AI實體用於控制角色單位
  • 創建並使用行為樹和黑板
  • 使用AI感知讓角色單位獲得視野
  • 創建行為讓角色單位四處走動並攻擊敵人

注意:本篇教程只是Unreal Engine 4系列教程的其中一篇:

起步入門

下載示例項目並解壓。進入項目文件夾,雙擊MuffinWar.uproject打開項目。

按下Play運行游戲,在圍欄內點擊左鍵生成蘑菇小人。

在本例中,我們將創建一個能四處走動的AI,當其他蘑菇小人進入AI的視野時,AI會追逐對方並進行攻擊。

要創建一個AI角色,我們需要三個元素:

  1. 身體:這個是角色的物理表現,在本例中,蘑菇小人就是身體
  2. 靈魂:這個是控制角色行為的實體,既能是玩家本身,也可以是AI
  3. 大腦: AI進行決策行為的邏輯,我們可以用C++代碼,藍圖或者是行為樹來實現邏輯。

現在我們已經有了身體,接着要搞來靈魂和大腦。首先,我們要創建控制器作為靈魂。

什么是控制器?

控制器是一個能控制角色單位的非物理Actor。這里所說的“控制”,具體指的是什么意思呢?

對於玩家而言,控制指的是能通過按鍵操控角色單位。控制器獲取玩家輸入,並將輸入直接傳給角色。當然,控制器也可以獲取輸入進行處理,然后再告訴角色單位做哪個行為。

對於AI來說,角色單位就是由控制器或“大腦”(取決於實現方式)來通知其做什么行為的。

為了用AI控制蘑菇小人,我們需要創建一類特殊的控制器——AI控制器

創建AI控制器

打開Characters\Muffin\AI目錄並創建Blueprint Class,選中AIController作為父類並命名為AIC_Muffin

接着,我們需要讓蘑菇小人使用這個AI控制器,打開Characters\Muffin\Blueprints並雙擊打開BP_Muffin

默認情況下,Details面板會顯示藍圖的默認設置,如果沒有顯示,就點擊Toolbar的Class Defaults

在Details面板找到Pawn設置,將AI Controller Class設為AIC_Muffin,這樣當蘑菇小人生成時,就會對應生成一個AI控制器實例。

由於我們要動態生成蘑菇小人,Auto Possess AI要設成Spawned。這樣當蘑菇小人生成時,AIC_Muffin就會自動控制BP_Muffin

點擊Compile並關閉BP_Muffin

現在,我們要來創建決策蘑菇小人行為的邏輯,就要用上行為樹

創建行為樹

打開Characters\Muffin\AI目錄,並選擇Add New\Artificial Intelligence\Behavior Tree,將其命名為BT_Muffin並打開。

行為樹編輯器

行為樹編輯器包含3個新面板:

  1. Behavior Tree:這個圖表面板用於創建行為樹節點
  2. Details:展示選中節點的參數
  3. Blackboard:展示黑板的所有鍵值(后續講解)和其對應數值。只有在游戲運行時才會有顯示

像藍圖一樣,行為樹也是由節點構成的。行為樹有4類節點,前兩種分別是任務(tasks)組合(composites)節點。

什么是任務和組合節點?

顧名思義,任務節點負責完成具體任務,可以是表現一套連招這樣的復雜任務,也可以是原地等待這樣的簡單任務。

要完成多個任務,我們就要用上組合節點。一個行為樹由許多分支(行為)組成。每個分支的根節點,都是一個組合節點。不同類型的組合節點,執行其子節點的方式也各不相同。

比如,我們有一組如下序列的行為:

要按順序執行每個行為,我們就要用上Sequence組合節點,因為Sequence節點能夠從左至右的執行子節點,圖表看起來是這樣的:

注意:從組合節點衍生出來的節點可以稱為子樹(subtree)。通常來說,這些節點就統稱為一個行為。比如,SequenceMove To EnemyRotate Towards EnemyAttack就統稱為“攻擊敵人”行為。

如果Sequence的任意節點執行失敗,整個Sequence節點就會停止執行。

比如,如果角色無法移動到敵人身邊,Move To Enemy節點就執行失敗了,這樣Rotate Towards EnemyAttack節點也就無法繼續執行了。反之,如果角色成功移動到敵人邊上,就能執行隨后兩個節點。

后續我們還會學習Selector組合節點,不過現在先讓我們用Sequence節點實現角色隨機移動到某個位置並原地停留。

隨機移動位置

首先,創建Sequence節點並與Root節點相連。

接着,我們需要讓角色移動起來,創建MoveTo節點與Sequence節點相連,這個節點可以驅動角色移動到特定位置或Actor。

隨后,創建Wait節點與Sequence節點相連,確保將其放置在MoveTo節點右邊,放置順序非常重要,因為子節點是按照從左到右的順序執行的。

注意:你可以通過每個節點右上角的數字確認其執行順序。數字越小執行順序越高。

恭喜你,你剛剛創建了你的第一個行為!它將會驅動角色移動到指定位置並原地停留數秒。

為了讓角色移動,我們還需要指定要移動的位置。由於MoveTo節點只接受由黑板提供的數值,我們要先創建一個黑板。

創建黑板

黑板是一個單純用來存放變量(鍵值)的資源。我們可以將其理解為AI的內存。

雖然黑板不是必須使用的,但它確實為我們讀取,存取數據提供了極大便利,這么說的原因是很多行為樹節點只接受黑板鍵值作為參數輸入。

要創建一個黑板,我們在Content Browser選擇新建Add New\Artificial Intelligence\Blackboard,將其命名為BB_Muffin並打開。

黑板編輯器

黑板編輯器由2個面板組成:

  1. Blackboard:展示所有鍵值列表
  2. Blackboard Details:展示所選鍵值的參數

現在,我們要創建一個鍵值用於存放目標位置。

創建目標位置鍵值

由於是3D空間里的一個位置點,我們需要用Vector來進行存儲。點擊New Key並選擇Vector,將其命名為TargetLocation

接着,我們需要隨機生成一個位置並將其存在黑板里,我們就需要用到第三種類型的行為樹節點:服務(service)節點。

什么是服務節點?

服務節點類似於任務節點,用於完成一些事情。然而,不同於操控角色做特定行為,服務節點用於執行檢查或更新黑板操作。

服務並不是獨立節點,而是依附於任務節點或者組合節點。這樣使得行為樹更加簡潔易於組織,不會橫生太多節點。如果我們用任務節點來實現,效果如下圖所示:

如果用服務節點來實現,則如下圖所示:

現在,讓我們來創建一個生成隨機位置的服務吧。

創建服務

回到BT_Muffin並點擊New Service

這樣就會新建一個服務並自動打開,我們回到Content Browser將其重命名為BTService_SetRandomLocation

服務應當且僅當在角色准備移動時才執行,因此我們要將它附着在MoveTo節點上。

打開BT_Muffin右鍵點擊MoveTo節點,從彈出菜單選擇Add Service\BTService Set Random Location

現在,當MoveTo激活執行時,BTService_SetRandomLocation也會跟着激活執行。

接着,我們需要隨機生成目標點位置。

生成隨機位置

打開BTService_SetRandomLocation

為了監聽獲知服務何時觸發執行,我們創建Event Receive Activation AI節點,這個節點會在服務父類(所附着的節點)激活時觸發執行。

注意:另一個事件Event Receive Activation也有着相同的觸發時機,兩者區別在於Event Receive Activation AI事件額外提供了Controlled Pawn參數。

為了生成隨機位置,添加如下高亮節點,確保將Radius設置為500

這樣就能返回得到該角色500單位半徑內的一個隨機可達目標點。

注意:GetRandomPointInNavigableRadius節點使用了導航數據(稱之為NavMesh)來判斷一個點是否可達。在本例中,我已提前創建好了NavMesh。你可以通過在Viewport選中Show\Navigation觀察NavMesh。


如果你想創建自己的NavMesh,請創建 Nav Mesh Bounds Volume,縮放其大小為理想可達區域。

 

接下來,我們需要將位置數據存儲到黑板里。有兩種方式指定要存放的鍵值:

  1. 我們可以使用Make Literal Name節點指定鍵值名字
  2. 我們可以將變量暴露給行為樹,這樣就能在行為樹里通過下拉列表選中變量

這里我們使用第二種方法。創建類型為Blackboard Key Selector的變量。將其命名為BlackboardKey並啟用Instance Editable,這樣行為樹里的服務就會出現對應變量。

隨后,創建如下高亮節點:

小結:

  1. Event Receive Activation AI節點會在其父類(本例中的MoveTo節點)激活時執行
  2. GetRandomPointInNavigableRadius節點返回角色500單位半徑內的一個隨機可達目標點
  3. Set Blackboard Value as Vector節點將一個黑板鍵值(BlackboardKey)數值設為隨機位置點

點擊Compile並關閉BTService_SetRandomLocation

接着,我們需要讓行為樹來使用這個黑板值。

使用黑板

打開BT_Muffin並確保沒有選中任何東西。在Details面板的Behavior Tree設置處,將Blackboard Asset設為BB_Muffin

然后MoveToBTService_SetRandomLocation就會自動使用黑板的第一個鍵值,在本例中,就是TargetLocation

最后,我們需要讓AI控制器來運行行為樹。

運行行為樹

打開AIC_Muffin並連接Run Behavior Tree節點與Event BeginPlay節點,將BTAsset設為BT_Muffin

這樣當AIC_Controller生成時就會執行BT_Muffin

點擊Compile並返回主編輯器,按下Play運行游戲,生成一些蘑菇小人,觀察它們四處走動吧。

雖然設置很繁瑣,我們還是搞定了!接着,我們要進一步設置AI控制器,讓它可以在一定范圍內感知敵人所在。要實現這點,就要使用AI感知(AI Perception)

設置AI感知

AI感知是一個可以添加給Actor的組件,通過它,我們可以給AI添加感官能力(如視覺和聽覺)

打開AIC_Muffin並添加AIPerception

接着,我們要添加一個感官,由於我們想要蘑菇小人能夠感知到其他小人靠近,我們給它加上視覺感官。

選中AIPerception並在Details面板的AI Perception設置處,給Senses Config添加新元素。

將元素0設置為AI Sight config並展開它。

對於視覺有3個主要設置:

  1. Sight Radius:蘑菇小人的最遠視覺范圍,將其設置為3000
  2. Lose Sight Radius:如果蘑菇小人已經看到了敵人,那敵人要逃離小人視野的距離,將其設置為3500
  3. Peripheral Vision Half Angle Degrees:決定蘑菇小人視野的角度,將其設置為45,蘑菇小人就會有90度的范圍視角。

默認情況下,AI感知只檢測敵人(被指定為不同隊伍(team)的Actor)。然而,Actor默認是沒有設置隊伍的,如果Actor沒有隊伍,AI感知就會將其認為中立(neutral)角色。

截至目前,還沒有方法能通過藍圖設置Actor的隊伍,退而求其次,我們展開Detection by Affiliation設置,啟用Detect Neutrals

點擊Compile並回到主編輯器。按下Play運行游戲來生成蘑菇。按下 ‘ 鍵可以顯示AI調試信息,按下小鍵盤的數字鍵4可以可視化AI感知組件。當蘑菇小人進入視野時,就會顯示綠色球體。

接着,我們要讓蘑菇小人往敵人的方向走去。要實現這點,行為樹就要了解敵人的信息,我們通過在黑板存儲敵人的引用來完成這件事。

創建敵人鍵值

打開BB_Muffin並添加類型為Object的鍵值,將其命名為Enemy

現在,我們還不能在MoveTo節點使用Enemy,因為其鍵值類型為Object,但MoveTo只接受VectorActor類型的鍵值。

為了解決這點,我們選中Enemy並展開Key Type,將Base Class設置為Actor。這樣行為樹就能將Enemy識別為Actor了。

關閉BB_Muffin,現在,我們要創建一個行為讓AI向敵人走去。

朝敵人移動

打開BT_Muffin並斷開SequenceRoot連接。我們可以通過按住Alt鍵點擊連線來做到,並將移動子樹移到一邊。

接着,創建如下高亮節點,並將Blackboard Key設置為Enemy

這樣角色就會朝Enemy走去。有時候,角色不會剛好面對着它的目標,所以我們還需要用上Rotate to face BB entry節點。

現在,我們需要在AI感知檢測到其他蘑菇時,將其設置為Enemy的值。

設置敵人鍵值

打開AIC_Muffin並選中AIPerception組件,添加Perception Updated事件。

只要感官發生更新,這個事件就會觸發執行。在本例中,當AI獲得或丟失了某物體的視野,這個事件就會執行,並提供了其當前所能感知到的Actor列表。

添加如下高亮節點,並確保將Make Literal Name節點設置為Enemy

這樣就可以判斷AI目前有沒有敵人對象,如果沒有,我們就要給它設置一個敵人,因此添加如下節點:

小結:

  1. IsValid節點負責判斷Enemy鍵值是否有值
  2. 如果還沒設置,遍歷當前所有檢測到的Actor
  3. Cast To BP_Muffin節點負責檢查Actor是否為蘑菇
  4. 如果是蘑菇,進一步判斷是否已死亡
  5. 如果IsDead返回false,將蘑菇設置為新敵人,並退出循環

點擊Compile並關閉AIC_Muffin,按下Play運行游戲並生成兩個蘑菇小人,其中一個生成暴露在另一個面前,后者就會自動向前者走過去。

接着,你要創建一個自定義任務,讓蘑菇小人可以表演攻擊行為。

創建攻擊任務

我們可以直接在Content Browser創建任務,而無須通過行為樹編輯器。創建新的Blueprint Class類,並將BTTask_BlueprintBase作為其父類。

將新建類命名為BTTask_Attack並打開,添加Event Receive Execute AI節點,這個節點會在行為樹激活BTTask_Attack時觸發執行。

首先,你需要讓蘑菇執行攻擊行為。BP_Muffin包含一個IsAttacking變量,當變量設置為true時,蘑菇會執行一次攻擊,因此我們添加如下高亮節點:

如果這個任務節點在這里就結束了,那行為樹執行就會卡在這個節點上,因為行為樹並不知道該節點已執行完畢了,所以我們要在節點鏈末端添加Finish Execute節點。

接着,啟用Success,由於我們用的是Sequence,這樣就能讓BTTask_Attack的后續節點得以執行。

現在圖表看起來應該是這樣的:

小結:

  1. 當行為樹激活BTTask_Attack節點時,Event Receive Execute AI節點就會一同觸發執行。
  2. Cast To BP_Muffin節點會檢查Controlled Pawn是否為BP_Muffin類型
  3. 如果是,則設置IsAttacking變量為true
  4. 通過Finish Execute節點退出當前節點,讓行為樹繼續往下執行

點擊Compile並關閉BTTask_Attack

現在,我們需要將BTTask_Attack節點添加到行為樹中。

行為樹添加攻擊行為

打開BT_Muffin,隨后,將BTTask_Attack節點添加到Sequence節點后面。

接着,將Wait節點添加到Sequence節點后面,並將Wait Time設置為2。確保蘑菇小人不會攻擊個不停。

回到主編輯器點擊Play運行游戲,像上次一樣生成兩個蘑菇小人。蘑菇小人會朝着敵人走去。隨后,它會嘗試攻擊,然后休息兩秒。當它發現另一個敵人時,又會重復以上行為。

在最后一部分,我們要將攻擊和移動兩顆子樹合並在一起。

合並子樹

為了合並子樹,我們要用上Selector組合節點。類似於Sequence節點,它也是按從左向右的順序執行的。然而,Selector節點會在子節點返回成功而非失敗時停止執行。利用這個特性,就可以確保行為樹每次只執行一顆子樹。

打開BT_Muffin並在Root節點下創建Selector節點。隨后,如下圖連接兩個子樹:

這樣同一時間只有一顆子樹會得到執行,下面是每顆子樹的執行情況:

  • 攻擊: Selector節點會首先運行第一顆子樹,如果所有任務都成功了,Sequence節點也會返回執行成功。Selector節點得知執行成功,就會停止執行后面的節點,這樣就不會再執行移動節點。

  • 移動: Selector節點會嘗試運行前面的攻擊子樹,如果Enemy還沒有值,MoveTo節點就會執行失敗,Sequence節點也就同樣失敗。由於第一個子樹失敗了,Selector節點就會執行后續這顆移動子樹。

回到主編輯器,按下Play運行游戲,生成一些蘑菇小人試試看吧!

“等等,為什么圖中這個蘑菇小人沒有馬上攻擊另一只呢?”

在傳統的行為樹設計里,行為樹每幀都會從根節點開始執行,意味着每幀更新,它都會嘗試執行第一顆攻擊子樹,然后再執行第二顆移動子樹,這也意味着當Enemy值發生變化時,行為樹就會馬上切換執行另一顆子樹。

然而,Unreal的行為樹並不是這樣設計執行的。在Unreal里,行為樹會繼續執行上一幀選中的那顆子樹。圖中由於AI感知沒有馬上感知到另一只蘑菇小人的存在,行為樹開始執行移動子樹,於是行為樹就只能乖乖等待移動子樹執行完畢,才能重新評估確定執行攻擊子樹。

為了解決這個問題,我們需要用上最后一種類型節點:裝飾(decorators)節點。

創建裝飾節點

類似於服務節點,裝飾節點也依附於任務或組合節點。通常而言,裝飾節點用於做前置檢查。如果檢查結果為true,裝飾節點就返回true,反之亦然。通過裝飾節點,就能控制其依附節點是否能夠執行。

裝飾節點也有能力中止子樹的運行,這意味着我們能實現一旦Enemy有設值,就立即中止移動子樹。這樣蘑菇小人就能在發現敵人的第一時間攻擊敵人。

要實現中止功能,我們可以使用Blackboard裝飾節點,這個節點只是簡單地檢查某個黑板鍵值是否有值。打開BT_Muffin,並在攻擊子樹的Sequence節點點擊右鍵,從彈出菜單選中Add Decorator\Blackboard,這樣Sequence節點就會添加上Blackboard節點。

接着,選中Blackboard裝飾節點,並在Details面板將Blackboard Key設為Enemy

這樣可以判斷Enemy是否有值,如果沒有值,節點返回失敗,從而導致Sequence失敗,從而讓移動子樹得到執行。

為了中止移動子樹,我們需要用上Observer Aborts設置。

使用Observer Aborts

Observer Aborts能夠實現所選中的黑板鍵值發生變化時,中止執行子樹,這里分為兩種類型的中止:

  1. Self: 該設置允許當Enemy值失效時,立即中止運行攻擊子樹,這種情況發生在攻擊子樹還未運行完畢,而Enemy又死亡的時候。
  2. Lower Priority:該設置允許當Enemy有值時,中止運行較低優先度的子樹。由於移動子樹放在攻擊子樹后面,它就是較低優先度子樹。

我們將Observer Aborts設為Both,同時啟用兩種類型的中止。

現在,當AI已經沒有敵人目標時,可以馬上從攻擊子樹切換運行移動子樹。同樣的,當AI檢測到敵人目標時,又能從移動子樹切換運行攻擊子樹。

以下是完整的行為樹圖表:

攻擊子樹小結:

  1. Enemy有值,Selector開始運行攻擊子樹
  2. 一旦運行子樹,角色開始朝敵人走去
  3. 隨后,進行攻擊
  4. 最后,角色原地停留2秒

移動子樹小結:

  1. Enemy沒有值,攻擊子樹運行失敗時,Selector繼續運行移動子樹
  2. BTService_SetRandomLocation生成一個隨機位置
  3. 角色朝指定位置移動
  4. 隨后,角色原地停留5秒

關閉BT_Muffin並按下Play運行游戲,生成一些蘑菇小人進行一場你死我活的決斗吧!

后續學習

你可以在這里下載完整項目。

如你所見,制作簡單AI還算一件不難的事。如果你想創建一個更加高級的AI,請查閱場景查詢系統,這個系統允許AI收集場景數據並作出相應的反饋。

如果你還想繼續學習引擎其他內容,點擊下篇教程,將教你如何制作一個簡單的第一人稱射擊游戲。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM