前言
之前實現了自主創作的角色導入進UE4並成功控制其進行一系列動作,但目前的樣子距離基本的游戲架構還差了一個很大的模塊:NPC,而這部分是由電腦來進行自動控制,所以,我有一句話不知當講不當講(對,我又不滿足了( •̀ ω •́ )✧)。由此,我又一次打開了官方文檔,開始對UE4中比較難啃的AI模塊進行探索。(前方少圖,請放心加載(笑))
正文
一.構成
先說一下UE4中AI的構成,一般如果是對玩家有威脅的敵人角色或者是跟隨玩家的npc角色,它們的配置一般有:1.行為樹;2.黑板;3.AI控制器;4.AI角色;5.相關服務;6.相關修飾器;7.相關任務。本次以類似於《合金裝備》里敵兵的AI構建為例,對各個配置進行說明與個人見解。
1.行為樹
UE4的AI角色核心,屬於決策層配置,大致樣貌如下:
如果你有一些程序設計基礎,那么大致通過名字就能判斷行為樹是由類似於if—else一類的決策組成的AI行動方案。並且行為樹是一種可以進行類似深度優先遍歷操作的數據結構,即比如在某個子樹的行為已經運行完成后,狀態改變,則此時根據改變的條件選擇需要遍歷的子樹接着進行遍歷與運行操作。而組成行為樹的節點可並不是類似於普通樹狀結構中單一僅含有權重的“小圓圈”,行為樹一般由以下幾種節點組成:Root,Composites和Tasks。接下來分別描述一下三種節點:
a.Root節點
又名根節點,是整個行為樹的核心與基礎,一但當Agent在場景中的實例化被調用時,那么,Root就是行為樹的調用入口,從此處開始AI行為邏輯。由於其固定的性質,所以Root節點不可進行編程。
b.Composites節點
直譯過來是“復合體”,根據其節點的作用,又分為Sequence,Selector和Simple Parallel(此節點暫不做敘述),其中,Sequence(隊列)是為了讓其下的子節點或子樹按照順序標號的順序進行執行的節點,而Selector則是行為樹中最重要的“選擇單元”,通過某些“特殊”的“玩意兒”實現對於狀態(一般是用整型,浮點,或枚舉表示狀態)的判斷,從而選擇相關的子樹。與Root同樣含有固定的性質,所以不可編程。
c.Tasks節點
又名任務節點,即上述的“相關任務”,是相同的東西,這里具體記載着Agent的行為,當遍歷執行到這里時,Agent便會做出行動,比如跑,跳等。由於AI的行為依據具體情況決定,所以在引擎默認創建的幾個Tasks節點之外,開發者還可以編寫新的Tasks節點,即Tasks節點是可編程的(行為樹插圖中紫色的部分)。
2.黑板
黑板是用來存儲一系列Agent狀態變量的一種結構體,比如此時Agent發現了玩家並且向玩家跑過來,所以它此時的狀態至少就應該有這么幾個:玩家此時的位置,Agent的移動類型(Idle或者Running)以及玩家最后一次出現的位置,這些東西都要用變量存儲起來。然而各個任務節點與一系列的服務,修飾器之間是毫無聯系且相互獨立的,所以他們之間並不能直接修改對方的數據,這時候就要通過黑板里面的數據作為接口實現整個系統之間的通信。並且實際上這些數據並沒有保存在黑板里面,黑板只是提供了一種定義方式,行為樹會自行創建一個黑板,將所有數據存儲在那里面,只是行為樹創建的黑板會遵從原黑板的數據格式罷了。真不知道Epic官方是怎么起名的,叫“記憶體”或“數據塢”這種高大上狂拽酷炫屌炸天的名字它不香么?(或許Epic有這種想法的合理性吧,咱對人工智能的基礎理論知之甚少,如果有哪位大佬知道,歡迎補充)
3.AI控制器與AI角色
這便是AI在游戲場景中的實例化,也是最終展現給玩家的樣貌。由控制器控制角色進行相應的動作,由行為樹作為多觸手章魚玩家來操作多個控制器從而控制多個角色。
4.服務
開發人員以及玩家們都希望在做出一系列的舉止后(比如制造噪聲,向敵人扔個小黃書等),Agent可以根據這種舉止做出相應行動,即遵守圖靈機的基本定義“輸入一定的數據——處理數據——輸出相應的處理結果”。試想,如果不接受玩家的輸入從而在場景內亂跑,那豈不就與喵星人的掃地座駕以及辣個女人一樣么?
所以,服務就是來干這事的。通常,服務一般用來接收此時場景內的情況輸入,比如玩家進入視錐,並且玩家並沒有躲在牆體后面,這時服務就會獲取這些信息的輸入,並且通過邏輯處理從而將黑板中的狀態數據進行改變並更新。當然,由於一個Agent的行為在不同的游戲類型中(甚至不同關卡中)會接受多種不同的輸入,並且黑板上的數據也並不是一定相同的,所以,服務完完全全是可編程的(行為樹插圖中青色的部分)。
5.修飾器
好的,這時候我們已經獲取了環境的信息並改變了Agent的狀態,如果我們希望Agent根據此時的狀態做出相應的行為,那么就需要Selector進行選擇。然而,如何讓Selector進行選擇,總不能隨機選一個?如果是這樣,那么Selector以及Agent的狀態便毫無意義所以,這便是“那個玩意兒”——修飾器的作用:作為執行其負責的子樹部分的判定條件,也就是if—else結構中括號里填寫的那個條件,即定義的條件滿足后再運行相應子樹,若不滿足則退回上一節點。一般都使用基於參考黑板數據的修飾器。當然,判定條件也有可能與關卡狀態,玩家狀態甚至是你的電腦狀態等黑板中並沒有存儲的數據相關,所以,修飾器也是可編程的。(行為樹插圖中藍色的部分)
二.聯系
接下來以我最喜歡的PS2游戲之一《合金裝備2:自由之子》(我絕對不是因為可以在廁所里看海報而喜歡的)其中一個場景進行說明(請結合行為樹插圖食用更佳),具體場景如下所示:
此時玩家操縱的蛇叔處在A點,他要前往的B點(rush B)附近有敵兵看守,此敵兵假如說就是我們描寫的Agent,並且在相應的黑板中創建了這樣幾個變量State(Agent的狀態,枚舉類型:Idle,Combat,Searching),Player(准確的說是Agent前往的地點),PlayerMarker(玩家在Agent可視范圍消失后最后一次出現的位置);這時,Agent在場景中實例化,所以,引擎會調用Agent所使用的行為樹的Root節點,此時行為樹開始工作。首先這時Agent面向牆壁,而在行為樹中的EnemyTrial_SearchVision服務為Agent正方向創造了一個110度(角度)的視錐,此時玩家並沒有在視錐范圍內,所以EnemyTrial_SearchVision服務將此時Agent的State設置為Idle,目標前往地點與玩家最后一次的位置設置為空。接下來進行選擇,Selector會通過自己所有分支中的修飾器進行選擇,既然此時狀態為Idle,那么State equal Idle修飾器符合條件,所以接下來執行此修飾器下的子樹,然后此時Agent的行為由EnemyTrial_SetWalkSpeed(設置此時Agent的行走速度:巡邏速度)以及EnemyTrial_MoveToRandomPoint(讓Agent隨機移動到某點)定義並控制Agent執行。在執行的過程中,服務以及修飾器也沒閑着,一旦此時Agent隨機移動的點在A點附近,Agent需要轉身,而此時玩家操作的蛇叔正好探頭與Agent對上了眼(進入視錐)。那么,服務會立即改變此時Agent的State為Combat,而且將Player與PlayerMarker設置為此時玩家的位置,修飾器監視到State改變,所以會立即中斷自身的行為,並將進度立即返還到Selector,然后由Selector分配給State equal Combat,接着執行Combat對應的Agent行為。但是,如果此時蛇叔打開了光學迷彩(這是不可能的,因為從橋上跳下來時已經摔壞了),在視錐中消失,這時服務將Player設置為空,並會檢索黑板中的PlayerMarker,如果PlayerMarker非空,則Agent的Selector將進度交給State equal Searching,總不能Agent看不到敵人就放棄搜索吧(山貓:你已經被開除了),如果PlayerMarker為空,那么Agent的Selector會將進度交給State equal Idle(一般這種情況不可能,除非玩家開了修改器)。接着,當Agent到達PlayerMarker后,在其搜索的時間內如果沒有在視錐內發現玩家,那么EnemyTrial_DestroyMarker任務便會清空Player與PlayerMarker,並且將State設置為Idle,這時修飾器監測到了State的改變,所以此時又會將進度返還給Selector,再由Selector將進度交給State equal Idle,實行Agent基礎的巡邏狀態。
結語
又開了一個坑……我也想好好做個游戲啊%&*#@!¥%%#(因言語過激而被踢出直播間),但是基礎不足,身邊也沒有學習美工與音效的小伙伴,只能自己慢慢摸索了,C++也在補STL方面的基礎,不過這倒是充足了網課后的課余時間(笑)。如果我的這篇文章里面的相關名詞與解釋有不正確或是容易引發歧義的地方,歡迎在評論區指出。而且如果你對於其中的一些解釋還有不明白的地方,也歡迎騷擾。當然,老規矩,如果你認為這篇文章會對更多的人有幫助,歡迎轉載,只不過請注明轉載出處即可,以上。どうもありがとうございました!(都莫,阿里嘎多靠薩依瑪希大!)