AI PRO I 第4章 譯文 Behavior Selection Algorithms An Overview
阿新 • • 發佈:2020-10-19
Behavior Selection Algorithms
An Overview
Michael Dawe, Steve Gargolinski, Luke Dicken,
Troy Humphreys, and Dave Mark
翻譯:TraceYang,錢康來
本文將介紹一些遊戲行業中最為流行和已被驗證過的決策演算法,當可能是最佳使用選擇時,提供這些選擇的概述並簡單介紹這些演算法,以及使用場景。雖然本文並不是綜合性資源,但希望對對AI程式設計師選擇各種演算法來使用來說是一個不錯的介紹。
FSM的一個吸引人的特性是很方便畫出和視覺化,每個圓角矩形代表一個狀態,用箭頭連結兩個盒子來代表狀態間的轉換。轉換箭頭上的標籤是觸發轉換的必要條件。實心圓代表初始狀態,當FSM初次執行時會進入這個狀態,假設我們為保衛城堡的守衛設計了FSM,如圖4.1所示。
圖4.1 FSM的圖代表了一個守衛NPC的行為
我們的守衛NPC會在巡邏(Patrol)狀態開始,並關注城堡中屬於他負責的部分。如果他聽到了什麼聲音,就會脫離巡邏狀態,移動並調查(Inverstiage)一下噪音再返回巡邏狀態。如果發現了敵人,就會進入攻擊(Attack)狀態以對抗威脅,在攻擊時,如果他的生命值過低,他會逃跑保命(Flee),如果他打敗了敵人,會返回巡邏狀態。
雖然有很多可行的FSM的實現,這裡看下演算法實現的示例還是有幫助的,首先是FSMState類,我們每個具體狀態都通過它來擴充套件:
4.1 介紹
當家用機玩家對他們購買的遊戲要求更高的時候,為遊戲編寫AI系統變得越來越難了。同時,一些移動平臺的小遊戲也突然開始活躍了,這使得讓AI程式設計師知道如何在短時間內獲得最佳的行為變得重要起來。 即使是強力的機器上執行的複雜遊戲,NPC的種類很多,簡單的例如跑過去或獵殺的簡單動物,複雜的可以讓玩家用幾個小時來互動的成熟的同伴角色。雖然這些示例的AI也是遵循著感覺-思考-行動的迴圈,但這個迴圈中“思考”部分仍然是不明確的。有各種演算法來從中選擇,每種演算法都適合不同的用途。例如對於實現最近的家用機遊戲里人類角色的最佳選擇,並不適合來建立基於網路的棋盤遊戲中的對手。4.2 有限狀態機(Finite-State Machines)
有限狀態機(FSM)是現今遊戲AI程式設計最常見行為建模演算法。FSM的概念很簡單,實現起來也很方便,因為可以用很小的開銷產生出強力且靈活的AI架構。他們直觀而且方便視覺化,有助於非程式背景的成員之間的溝通,每個AI程式設計師都必須熟悉用FSM工作,並且可以意識到它們的優勢與缺點。 FSM可以把NPC的所有AI分解成稱作狀態(State)的小的離散的部分。每個狀態表示一個特定的行為或內部配置,而同時只有一個狀態被認為是“有效”的。狀態之間用轉換(transitions)來連線。當滿足特定條件時,有的連結會負責切換到新的有效狀態。class FSMState
{
virtual void onEnter();
virtual void onUpdate();
virtual void onExit();
list<FSMTransition> transitions;
};
class FSMTransition
{
virtual bool isValid();
virtual FSMState* getNextState();
virtual void onTransition();
}
class FiniteStateMachine
{
void update();
list<FSMState> states;
FSMState* initialState;
FSMState* activeState;
}
- 呼叫activeState.transtitions列表裡每個狀態轉移的isValid() 函式,直到有isValid()返回true,或者沒有狀態轉移發生。
- 如果找到有效的狀態轉移,那麼:
- 呼叫activeState.onExit()
- 設定activeState為validTransition.getNextState()
- 呼叫activeState.onEnter()
- 如果沒有找到有效的轉換,則呼叫activeState.onUpdate()
4.3 分層有限狀態機(Hierarchical Finite-State Machines)
FSM是非常有用的工具,但也有它的弱點。為NPC的FSM新增第2,3,4個狀態通常在結構上還是很常見的,因為這些都需要與現有的狀態之間關聯轉換(transition)的。但如果到了接近專案結束,FSM已經參雜了20,30,40個已有狀態,那麼要適應新的狀態到已有的結構裡是非常困難而且容易出錯的。 也有一些常見的情況是FSM不適合處理的,例如處境行為(situational behavior)的重複使用。作為示例,圖4.2展示了一個守夜人角色NPC負責保衛建築的保險箱。 圖4.2 FSM圖表代表了警衛的行為 NPC會一直簡單的在前門與保險箱之間巡邏。假如這時加入一個新的稱作對話(Conversation)的狀態,讓守夜人可以接電話並暫停下來進行短暫通話,然後再回去巡邏。如果守夜人接電話時是向大門方向巡邏,我們希望電話結束後他能重新向門的方向巡邏。同樣的,如果電話響起時他在向保險箱方向巡邏,電話結束後他也應該轉移回到巡邏保險箱的狀態。 因為我們需要知道接完電話後要回到哪個狀態,那就要強制建立一個新的對話(Conversation)狀態,每次都要複用這個行為,就如圖4.3所示的那樣。 圖4.3 我們的守夜人需要多個對話狀態的例項 在上面這個簡單的例子裡我們需要兩個對話行為才能完成,更復雜的FSM我們可能需要的就更多。每次都手動的新增這些我們希望重用的狀態並不是個好注意也不夠優雅。會導致狀態和圖表複雜度的爆炸,讓已有的FSM更難以理解而新的狀態也很難加入還容易出錯。 值得慶幸的是,有種技術可以緩解這種結構性上的問題:那就是分層有限狀態機(HFSM),在HFSM裡,每個獨立的狀態都可以是一個完整的狀態機。這種技術可以有效的把一個狀態機在層次裡分離成多個狀態機。 回到前面守夜人的例子,如果我們把兩個巡邏狀態放入稱作看守建築的狀態機裡,那麼就如圖4.4所示,我們只需要處理一個會話狀態。 圖4.4 HFSM解決了‘’對話“重複的問題。 這樣可以工作的原因是HFSM增加了FSM裡沒有的額外的滯後(hysteresis)。在標準的FSM裡,我們通常假定狀態機是從初始狀態開始,但在HFSM的巢狀狀態機裡並不是這樣。注意圖中被圈上的"H",指向的是‘’歷史狀態”。第一次進入巢狀看守建築的狀態機,歷史狀態會指定初始狀態,但從那以後,它指向的是這個狀態機當前有效的狀態。 在我們的例子裡,HFSM從看守大樓開始(由前面的實心圓和箭頭指示),選擇巡邏到保險箱(Patrol to Safe)為起始狀態。如果NPC到達了保險箱並轉換到巡邏大門(Patrol to Door),這時歷史狀態切換到巡邏大門。如果這時NPC這個時候響了,那麼HFSM會退出巡邏大門和看守建築,轉換到對話狀態。在對話結束後,HFSM會轉換回看守建築中的巡邏大門狀態(通過歷史狀態)。 如你所看到的,這個設定不需要複製任何狀態就實現了我們的設計目標。一般來說,HFSM提供了比狀態佈局更多的結構控制,允許更大,更復雜的行為分解到更小的更簡單的碎片裡。 HFSM的演算法更新類似FSM,因為巢狀的狀態機會增加遞迴複雜度。偽碼實現相當複雜,超出本文討論的範圍了。需要可靠的細節實現的話,可以看下Ian Millington and John Funge的著作《Artificial Intelligence for Games》的5.3.9章節。 FSM和HFSM都是為解決AI程式設計師通常要面對的寬泛問題的極為有效的演算法。就如討論過的,使用FSM有很多優點,但也有一些缺點。FSM潛在的不利因素在於你所期望的結構或許不能優雅的來適應結構。而HFSM在一些情況下可以幫助緩解這些壓力,如果FSM在狀態與狀態之間關聯方面“轉換過多”時,而HFSM的也不能提供幫助,其他的演算法可能是更好的選擇。4.4 行為樹(Behavior Trees)
行為樹描述的是從根節點開始由行為組成的資料結構,這些行為是NPC可以獨立執行的行動。每個行為都可以有子行為,這給了演算法像樹結構一樣的特徵。 每個行為都定義了先決條件(precondition),來指定代理在申請條件下執行行為,以及當執行行為時代理要實際做的行動。演算法從樹的根節點開始,並檢查行為的先決條件,依次決策每個行為。在樹的每一層,只能有一個行為被選擇,所以如果一個行為被執行了,他的兄弟行為就不需要檢查了,而它的子行為仍然要被檢查。相反,如果行為的先決條件並沒有返回true,那演算法就會跳過檢查它的子行為,並轉移到下個兄弟行為。一旦到達樹的終點,演算法會讓優先順序最高的行為執行,並依次執行每個行動。 演算法按下面的順序來執行行為樹:- 把根節點(root node)作為當前節點(current node)
- 還有當前節點存在
- 運行當前節點的先決條件(precondition)
- 如果先決條件返回true
- 把節點加入到執行列表
- 設定節點的子節點作為當前節點
- 如果返回不為true
- 設定節點的兄弟節點作為當前節點
- 執行所有在執行列表裡的行為
4.5 功能系統(Utility Systems)
大部分的AI邏輯以及相關的計算邏輯都是基於簡單的布林問題。例如代理體會問“能看到敵人麼?”或者“還有彈藥嘛?”這些都是純粹的“是”或“否”的問題。布林方程的決策往往是兩極分化的。這點在前面的架構裡也看到了,這種問題的結果通常會直接反應到一個行動上。例如:if (CanSeeEnemy())
{
AttackEnemy();
}
if (OutOfAmmo())
{
Reload();
}
if (OutOfAmmo() && CanSeeEnemy())
{
Hide();
}
4.6 目標導向的行動計劃(Goal-Oriented Action Planners)
目標導向的行動計劃(GOAP)技術是最早是Monolith’s Jeff Orkin與2005年在遊戲F.E.A.R中創造的,並在多個遊戲中使用,最近的遊戲是正當防衛2(Just Cause 2)和殺出重圍3:人類革命(Deus Ex: Human Revolution)。GOAP來自於1970年首次開發的斯坦福研究院AI解決問題方法(STRIPS)。一般來說,STRIPS(以及GOAP)允許AI系統通過提供的遊戲世界如何去運作的描述,建立它自己的方法來解決問題,這些描述可以是可能行動的列表,每種行動可以被使用的需求(稱作前提條件),以及行動的影響。這個系統接下來用象徵性表現世界的初始狀態,並需要設定一些需要達到的結果。在GOAP中,物體物件通常是根據預先設定的NPC希望完成的一組目標,通過一些例如優先順序或狀態轉換的方法來被選擇的。計劃系統會確定一些列的行動,讓代理來改變世界,從原來狀態向包含著需要的滿足目標的現實的狀態轉化。最經典的一種方式其實就是達到目標狀態的關鍵路徑,而目標是包含了所有客觀實時最容易達到的狀態。 GOAP通過“反向鏈查詢(backwards chaining search)”這來工作,這是個奇特的短語,因為著要從你要實現的目標著手,確定有什麼行動會變被要求產生,再找出要發生這些行動的先決條件。你繼續以這種方式工作直到到達你的初始狀態。這是一種在科學界已經失寵了的相當傳統的方法,被依靠啟發式搜尋,修正以及其他的竅門的“正向鏈查詢”替代了。向後查詢是個牢固的苦力,然後儘管他不夠優雅,比起現代技術它更容易理解和實現。 反向鏈查詢的工作方式如下:- 增加目標到未完成事情列表
- 對每個未完成事件
- 移除這個未完成事件
- 查詢影響這個事件的行動
- 如果滿足這個行動的先決條件
- 增加行動到計劃
- 反向工作增加已支援的行動鏈到計劃
- 如果不滿足
- 增加先決條件到未完成事件列表
4.7 層次任務網路(Hierarchical Task Networks)
雖然GOAP是最有名的遊戲計劃,但其他型別的計劃也應該得到普及。比如在Guerrilla Games的殺戮地帶2(KillZone)和High Moon Studios的變形金剛:塞伯坦之戰中使用的層次任務網路系統。就像其他的計劃一樣,HTN的目標目標是找到NPC要執行的計劃,而不是如何去做這個找到的計劃。 HTN從初始的世界狀態開始工作,主要任務是去我們需求要解決的問題。這個高層次任務會分解為越來越多的小任務,直到我們可以執行任務計劃來解決問題為止。每個高層次任務都有多種方法完成,當前的世界狀態來決定高層次任務將分解成那些小的任務組。這就允許可以在多個抽象層來決策。 而相對的反向計劃的GOAP,是從想要的世界狀態開始,反向移動到當前的狀態的世界。HTN是前向計劃,以為著它從當前世界狀態開始,朝所需的方案來運作。計劃從原始的狀態出發,能夠處理不同的基本事務。世界狀態代表了問題空間的狀態。遊戲術語中裡例子,可能是NPC在世界中的視口。遊戲狀態被分解成了多個屬性,如健康度,耐力,敵人的健康,範圍等等。這些知識的表述給予了計劃去做事的理由。 接下來,我們有兩個不同的任務:原始任務(primitive tasks)和複合任務(compound tasks)。原始任務是可以解決問題的可操作事項,遊戲術語裡可以是武器開火(FireWeapon),Reload(裝彈)和移動到掩體(MoveToCover)。這些任務可以被世界狀態影響,比如開火任務會使用彈藥和裝彈任務會重新裝填武器。複合任務是更高層的任務,可以用不同方法完成,作為方法(Method)來描述。而方法是可以完成複合任務的一組任務,以及確定方法何時使用的前提條件。複合任務允許HTN根據世界的原因來決定採取何種行動。 要使用複合任務,我們可以建立HTN域,域是代表了所有解決我們的問題方法的大的任務層次,比如行為是如何作為NPC的型別,下面的虛擬碼顯示瞭如何去構建計劃。- 將根複合任務加入到分解列表裡
- 對每個在分解列表裡的任務
- 移除任務
- 如果任務是複合的
- 找到世界中滿足複合任務的方法
- 如果找到方法,將方法任務加入到分解列表
- 如果沒有找到,恢復計劃到最後分解任務之間的狀態
- 如果任務是原始的
- 使用任務影響到當前的世界狀態
- 增加任務到最後的計劃列表裡