1. 程式人生 > 實用技巧 >AI PRO I 第4章 譯文 Behavior Selection Algorithms An Overview

AI PRO I 第4章 譯文 Behavior Selection Algorithms An Overview

Behavior Selection Algorithms An Overview Michael Dawe, Steve Gargolinski, Luke Dicken, Troy Humphreys, and Dave Mark 翻譯:TraceYang,錢康來

4.1 介紹

當家用機玩家對他們購買的遊戲要求更高的時候,為遊戲編寫AI系統變得越來越難了。同時,一些移動平臺的小遊戲也突然開始活躍了,這使得讓AI程式設計師知道如何在短時間內獲得最佳的行為變得重要起來。 即使是強力的機器上執行的複雜遊戲,NPC的種類很多,簡單的例如跑過去或獵殺的簡單動物,複雜的可以讓玩家用幾個小時來互動的成熟的同伴角色。雖然這些示例的AI也是遵循著感覺-思考-行動的迴圈,但這個迴圈中“思考”部分仍然是不明確的。有各種演算法來從中選擇,每種演算法都適合不同的用途。例如對於實現最近的家用機遊戲里人類角色的最佳選擇,並不適合來建立基於網路的棋盤遊戲中的對手。
本文將介紹一些遊戲行業中最為流行和已被驗證過的決策演算法,當可能是最佳使用選擇時,提供這些選擇的概述並簡單介紹這些演算法,以及使用場景。雖然本文並不是綜合性資源,但希望對對AI程式設計師選擇各種演算法來使用來說是一個不錯的介紹。

4.2 有限狀態機(Finite-State Machines)

有限狀態機(FSM)是現今遊戲AI程式設計最常見行為建模演算法。FSM的概念很簡單,實現起來也很方便,因為可以用很小的開銷產生出強力且靈活的AI架構。他們直觀而且方便視覺化,有助於非程式背景的成員之間的溝通,每個AI程式設計師都必須熟悉用FSM工作,並且可以意識到它們的優勢與缺點。 FSM可以把NPC的所有AI分解成稱作狀態(State)的小的離散的部分。每個狀態表示一個特定的行為或內部配置,而同時只有一個狀態被認為是“有效”的。狀態之間用轉換(transitions)來連線。當滿足特定條件時,有的連結會負責切換到新的有效狀態。
FSM的一個吸引人的特性是很方便畫出和視覺化,每個圓角矩形代表一個狀態,用箭頭連結兩個盒子來代表狀態間的轉換。轉換箭頭上的標籤是觸發轉換的必要條件。實心圓代表初始狀態,當FSM初次執行時會進入這個狀態,假設我們為保衛城堡的守衛設計了FSM,如圖4.1所示。 圖4.1 FSM的圖代表了一個守衛NPC的行為 我們的守衛NPC會在巡邏(Patrol)狀態開始,並關注城堡中屬於他負責的部分。如果他聽到了什麼聲音,就會脫離巡邏狀態,移動並調查(Inverstiage)一下噪音再返回巡邏狀態。如果發現了敵人,就會進入攻擊(Attack)狀態以對抗威脅,在攻擊時,如果他的生命值過低,他會逃跑保命(Flee),如果他打敗了敵人,會返回巡邏狀態。
雖然有很多可行的FSM的實現,這裡看下演算法實現的示例還是有幫助的,首先是FSMState類,我們每個具體狀態都通過它來擴充套件:

  1. class FSMState
  2. {
  3. virtual void onEnter();
  4. virtual void onUpdate();
  5. virtual void onExit();
  6. list<FSMTransition> transitions;
  7. };
每個FSMState都有3個不同的時機可以有機會執行邏輯,進入狀態時,離開狀態時,以及狀態有效且沒有發生轉換的每次Tick的時候。每個狀態還要負責儲存FSMTransition物件的列表,表示的是這個狀態的潛在轉換。

  1. class FSMTransition
  2. {
  3. virtual bool isValid();
  4. virtual FSMState* getNextState();
  5. virtual void onTransition();
  6. }
在我們的圖表中,每個轉換都由FSMTransition擴充套件而來。當滿足轉換條件時,isValid()函式的返回值會為true,然後用getNextState()得到要轉換到的有效狀態。當轉移開始onTransition() 函式就有機會執行所有必要的行為邏輯,類似FSMState裡的onEnter()函式。 最後,是FiniteStateMachine類:

  1. class FiniteStateMachine
  2. {
  3. void update();
  4. list<FSMState> states;
  5. FSMState* initialState;
  6. FSMState* activeState;
  7. }
FiniteStateMachine類中包含了我們所有狀態機的列表,以及初始狀態和當前有效狀態。還包含了核心的更新函式,負責呼叫每次Tick按下面的方式來執行我們的行為演算法:
  • 呼叫activeState.transtitions列表裡每個狀態轉移的isValid() 函式,直到有isValid()返回true,或者沒有狀態轉移發生。
  • 如果找到有效的狀態轉移,那麼:
    • 呼叫activeState.onExit()
    • 設定activeState為validTransition.getNextState()
    • 呼叫activeState.onEnter()
  • 如果沒有找到有效的轉換,則呼叫activeState.onUpdate()
有了這個結構,大致上就是設定轉換和onEnter(), onUpdate(), onExit(), 和onTransition() 函式裡填寫來產生所需的AI行為。具體的實現完全依賴設計。例如我們的攻擊狀態觸發一些對話,“他在哪,抓住他!”是在onEnter()裡說的,使用onUpdate() 來定期的選擇戰略位置,移動到掩體,向敵人開火等等。而攻擊和巡邏之間的轉換的onTransition()函式裡可以觸發一些額外的對話,例如“威脅消除!”。 在開始編碼你的FSM前,繪製一些像圖4.1那樣的表對定義行為邏輯和如何互相連結它們很有幫助。一旦理解了不同的狀態和轉換就可以開始編寫程式碼了。FSM是靈活且強力的,但只有在理解了核心邏輯之後,才能開發出可用的效果。

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
      • 設定節點的兄弟節點作為當前節點
  • 執行所有在執行列表裡的行為
行為樹的真正的優勢在於它的簡潔,因為它簡單的性質,基本演算法可以快速的實現。因為樹是無狀態的,演算法不需要記憶它前一個行為來決定當前幀要執行的行為。此外寫入的行為可以(也應該)完全不知道彼此,所以,從角色的行為樹裡新增或刪除行為不會影響樹其餘部分的執行。這就減輕了FSM的每個狀態都需要知道和其他狀態的轉換準則這個常見問題。 可擴充套件性也是行為樹的一個優點。讓它可以很容易的從基本演算法的描述開始,再開始新增額外的功能。通常我們在on_start/on_finish這兩個函式裡會新增行為樹行為。也可以實現不同的行為選擇器(selectors)。例如,父行為可以指定不選擇一個子行為來執行,而是讓所有的子行為都執行一次,或者是隨機選擇一個它的子行為來執行。事實上,如果需要的,子行為可以基於功能系統(utility system)型別的選擇器來執行。先決條件也可以響應事件來觸發,給予行為樹反應代理的刺激。另外一種流行的擴充套件是指定個別行為的非排他性,意味著如果他們的前決條件運行了,行為樹還是要繼續檢查這層裡其他兄弟行為。 儘管行為樹簡單和強力,但並不是總是選擇演算法的最佳選擇。因為每次都是必須是從選擇行為的根開始執行,它的執行時間通常要比FSM要長,而且隨意的實現會造成大量條件語句,這樣可能會非常慢,這取決於你的執行平臺。另一方面,評估樹裡的每一個可能的行為可能會影響到其他需要處理能力的事項這點也是限制因素。兩種演算法都可以是演算法的有效實現,因此程式設計師不得不去決定哪個是最好的。 因為行為自身是無狀態的,所以當建立的行為是要記憶的話就必須要小心。例如,想象一個市民從戰鬥中逃離,一旦原理戰鬥區域,“逃跑”行為就會停止執行,而最高優先的行為可能會把市民帶回到戰鬥區域,讓市民在這兩種狀態裡迴圈。雖然可以採取措施來防止這種問題,但傳統的策劃傾向更簡單的去處理這種情況。

4.5 功能系統(Utility Systems)

大部分的AI邏輯以及相關的計算邏輯都是基於簡單的布林問題。例如代理體會問“能看到敵人麼?”或者“還有彈藥嘛?”這些都是純粹的“是”或“否”的問題。布林方程的決策往往是兩極分化的。這點在前面的架構裡也看到了,這種問題的結果通常會直接反應到一個行動上。例如:

  1. if (CanSeeEnemy())
  2. {
  3. AttackEnemy();
  4. }
  5. if (OutOfAmmo())
  6. {
  7. Reload();
  8. }
即使當多個條件合併時,布林方程往往會導致一個非常離散的結果集合。

  1. if (OutOfAmmo() && CanSeeEnemy())
  2. {
  3. Hide();
  4. }
很多要決策的形式並不是太有條理,有很多問題是“是”或“否”來回答是不恰當的。例如我們要考慮敵人有多遠,還有多少發子彈剩下,有多餓,受傷狀況如何,或者是一些數量的連續值。相應的,這些連續值可以對映為如何去採取行動,而不是簡單的一個基於功能的系統,會用測量,稱重,合併,比率,等級以及排序等很多的考慮來決定潛在行為的可取性。使用上面的例子作介紹,我們可以評估有多強烈的需求去攻擊,裝彈和隱藏等等。 雖然功能技術可以用來實現其他架構中的轉換邏輯,但基於功能構建一個完整的決策引擎也是非常可行的。而實時上,有時建立一個基於功能的AI是遠優於其他方法的。可以讓遊戲包含很多可能的行動,以及不再是一個‘’正確”回答,而可以是基於大量競爭輸入的一個更好的行動。在一些情況下,我們將超越簡單的使用功能來測量或評價事物,而是它讓驅動實際執行的決策機制。用另外一種方式來說明,不再說“這是你要做的一個行動”,功能系統會建議“這裡有一些你可能去做的可選項”。 有個被充分證明的使用功能的例子是【模擬人生】,在這個遊戲裡,代理(也就是實際的模擬人),把他們環境中的資訊在內部狀態中結合起來,來達到一個每個潛在行為的更好的結果。舉個例子,當我很餓的時候,即使只有很少的食物我也會去獲得,反而當我不是很餓的時候就沒有吸引力。此外,接近“大量“的食物仍然是一個高優先的選項,即便只是“有點餓"。注意“非常多”,“一些”,”很少”,“一點”實際都是在最大和最小範圍間的值(典型的使用方法是0到1之間的浮點值)。 當要選擇一個新動作時(通常是因為當前動作結束或一些打斷系統),有一些候選方法可供選擇。例如,可以對潛在行為的分數排序,簡單的選擇“最合適”的行為-也就是分數最高的那個。另外一個就是用分數作為種子權重來隨機選擇。通過轉化隨機數為權重可行性,最合適的行為有較高的機會被選擇。當一個行為的適用性上升,它的分數會上升,被選擇的機會也提高了。 另外一個基於功能架構比其他架構更適合的示例是角色扮演遊戲(RPG)。通常在這類遊戲裡,代理體的選項通常是各種各樣的,可能根據環境只是微秒的更好或更壞。例如,選擇根據給予的敵人型別,代理體的狀態,玩家狀態來選擇用什麼武器,法術,道具或行為,可以是複雜的平衡行為。 另外,功能架構在一些遊戲系統中是作為更經濟的決策層來使用的。在RTS遊戲的創造單位還是建築的問題上,例如,這是有效組織消費,時間,和不同軸向上的優先順序(例如“攻擊”,“防禦”)。基於功能的架構往往能更適應不斷變化的遊戲環境。因此比起更多指令碼的模型,它可以更好的從混亂環境中恢復,指令碼模型容易出現絕望的困惑(受影響太大),或者根本不受到環境影響。 這種適應性的主要原因是最佳分數是高度動態的。當遊戲情況改變了-不管是環境變了還是代理的狀況變了-大多數的行動分數都會改變。當分數變化後,他們就可以選擇‘’合理“”的行為,這個結果受行為分數的起伏和流動,特別是與權重隨機選擇合併後,通常會產生動態的突發行為。 另一方面,和使用布林轉換決策邏輯的架構不同,功能系統通常會有不可預測性。因為基於特定的環境和上下文中行動有多少“意義”來進行選擇,行動看起來也更加合理。這種不可預測下有它的優點和缺點。它可以改善可信度,因為在特定環境下可以發生各種行動,這可以讓代理體看起來更加自然,而不僅僅是可預測的機械的基於if/then的模型。雖然在一些情況下是讓人滿意的,如果你要設計要求在特定時刻調的具體行為,就必須要用更多指令碼行為來覆蓋功能計算。 使用基於功能架構的另一個告誡就是,獲得微妙差別和可反應性往往是有代價的。雖然核心架構通常是相對簡單的設定,也可以簡單的新增新的行為,它們還是有一些挑戰的。很少有行為會在基於功能系統中孤立,相反的,它是被新增到一堆其他的有想法的潛在行為裡,相關的數學模型鼓勵合適的行為“冒泡到頂部”。這個訣竅(功能系統)是兼顧所有模型來鼓勵最合理的行為。比起科學更加藝術一些。與藝術一樣,產生的結果也往往比單獨使用簡單科學的更加迷人。 更多基於功能系統,可以看下這些書裡的文章。An Introduction to Utility Theory和Behavioral Mathematics for Game AI 。

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)”這來工作,這是個奇特的短語,因為著要從你要實現的目標著手,確定有什麼行動會變被要求產生,再找出要發生這些行動的先決條件。你繼續以這種方式工作直到到達你的初始狀態。這是一種在科學界已經失寵了的相當傳統的方法,被依靠啟發式搜尋,修正以及其他的竅門的“正向鏈查詢”替代了。向後查詢是個牢固的苦力,然後儘管他不夠優雅,比起現代技術它更容易理解和實現。 反向鏈查詢的工作方式如下:
  • 增加目標到未完成事情列表
  • 對每個未完成事件
    • 移除這個未完成事件
    • 查詢影響這個事件的行動
    • 如果滿足這個行動的先決條件
      • 增加行動到計劃
      • 反向工作增加已支援的行動鏈到計劃
    • 如果不滿足
      • 增加先決條件到未完成事件列表
GOAP一個有意思的地方是允許計劃系統忽視“上下文前提條件”,但在執行時必須滿足讓行動可以執行。這樣就可以繞過某些世界的某些不容易用符號表示的特定方面-例如要保證開火前目標在視線內,這需要去訪問一些非公開的資訊,從而保證這個好處理。這使得計劃GOAP產生是比較靈活的,呼叫的行動比起最基礎的執行水準更有戰術水平。也就是計劃告訴你要做什麼,但不是如何去做。例如,如何在開火前去建立視線細節說明可以被省略,也可以更被動的來處理。 讓我們假設有一個典型的NPC戰士角色,他的目標是殺死另外一個角色。我們用Target.Dead來表示我們的目標。為了讓目標去死,我們的角色需要射擊他(基礎系統)。射擊的先決條件是裝備武器,假設我們的角色沒有武器,就需要行動來給予這個角色武器,或者是在槍套裡繪製一個。當然這有一個先決條件,武器必須是在角色的道具欄裡。如果都滿足的話,我們就可以建立一個簡單的計劃來繪製武器並射擊。如果角色沒有武器怎麼辦?我們將不得不找到方法來獲取武器。如果尋找不能回溯和找到替代品來進行射擊行動,或許附近有安裝好的武器可以用來Target.Dead。在這兩種情況下,明顯提供一套世界裡可以去做的全面的行動選擇,我們就可以把它留給角色來決定去做什麼,讓動態和有趣的行為自然的出現,而不是在開發中想象和建立他們。 最後,要考慮遊戲中武器的最大範圍。作為上下文的前提條件,我們要求目標必須在射程內。策劃不會去花時間來讓它成為true,也不能,因為涉及了目標如何去移動的推理,但是也不會在條件滿足前開火,反而是會去找其他替代戰術或使用,例如射程更遠的不同武器。 很多人喜歡用基於自動計劃的方法來控制NPC。這樣可以讓設計人員專注於建立可以自我組成行為的簡單的元件,從而簡化了開發過程。也可以是“新奇”的解決方案,永遠不會被團隊預料,而作出的優秀故事往往是被玩家傳頌的。GOAP本身仍是最容易實現自動計劃的。從簡單的科學觀點來看,因為它的發展,藝術取得了長足的進步。也就是說,如果使用正確並提供了好的適應強的特殊定製的起點,它仍然是非常強大的技術。 值得注意的是,這種方法採用了以人物為中心的智慧,移除了很多開發團隊的創作和管理控制。成為我行我素的人,選擇的計劃更多的是完成角色本身的目標,而不會為了更加巨集偉的目標譬如生成更加逼真的遊戲體驗,這個可能就會破壞實現安排好的整體計劃(譬如一個士兵自己的計劃不會將自己帶到指定地點?)。 雖然使用已知的工程技術和有代表性的技巧可以避免這些型別的問題,但它的架構並不像行為樹那麼簡單,後者允許需要的行為可以直接注入到角色的決策邏輯裡。同時,GOAP方法比起基於層次任務網路更容易設計,因為GOAP中你只需要描述物體物件在世界中的流程。 GOAP和類似技術並沒有銀彈解決方案,但在合適情況下它們可以證實在創造現實計劃的工作從不同種類的原始的世界狀態開始行為和玩家可以完全互動的有身歷其境感的角色上是非常強力的。

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的型別,下面的虛擬碼顯示瞭如何去構建計劃。
  • 將根複合任務加入到分解列表裡
  • 對每個在分解列表裡的任務
    • 移除任務
    • 如果任務是複合的
      • 找到世界中滿足複合任務的方法
      • 如果找到方法,將方法任務加入到分解列表
      • 如果沒有找到,恢復計劃到最後分解任務之間的狀態
    • 如果任務是原始的
      • 使用任務影響到當前的世界狀態
      • 增加任務到最後的計劃列表裡
如前所述,HTN計劃是從非常高層次的任務開始,不斷的分解成小的更小的任務。這種分解是通過比較複合方法中每個方法在當前世界狀態中的條件來掌控的。當我們最終遇到原始任務時,就把它加入到最後的計劃裡。因為每個原始任務都是可操作的步驟,我們可以用它們來影響世界狀態,本質上就是不斷往前推動。一旦分解列表空了,我們就有了整套的遊戲計劃,或者沒有計劃而退出離開。 接下來演示HTN是如何工作的,假設遊戲的士兵NPC需要寫AI。根複合任務可以命名為BeSoldierTask。接下來,士兵是是否有敵人用來攻擊會有不同的行為。因此,就需要兩個方法來描述在這個情況下的做法。這種情況下的方法的任務可以叫做AttackEnemyTask,任務的方法定義了士兵可以攻擊的不同方式。例如,如果士兵的步槍有彈藥,就可以從掩體位置射擊,如果沒有彈藥,他會衝向敵人使用匕首攻擊,就需要給AttackEnemyTask寫兩種方法來完成這個任務。 我們越是深入士兵的行為,就有更多的形式和細化。域的組織結構自然的決定了一個人是如何描述他對另一個人的行為的。 因為HTN使用分層次結構描述行為,角色的塑造和論證都是用一種自然的方式,執行設計者更簡單的讀取HTN域,有助於程式和設計之間的協作。和其他的計劃系統一樣,AI實際的工作可以很好的儲存在模組化的原始任務裡,可以跨不同的AI角色來大量的重用。 因為HTN是通過圖來搜尋的,圖的大小會影響搜尋時間,但有兩種方法來控制搜尋大小。第一種是方法的條件可以用來過濾層次中的所有分支。這個在行為構建時可以天然的產生。還有一種方法是部分計劃可以把複雜計劃推遲到計劃執行的時候。例如複合任務AttackEnemy的考慮。一種可能是子任務NavigateToEnemy接著MeleeEnemy,NavigateToEnemy 需要尋路計算,不僅昂貴,而且會被世界狀態影響,這個可能會改變計劃和執行。通過利用部分計劃,把計劃分成兩種方法,而不是一個帶兩個子任務的方法:如果敵人在範圍外就NavigateToEnemy,在範圍內就MeleeEnemy 。這就執行我們當敵人超出範圍時只構建NavigateToEnemy的一部分計劃,縮短了搜尋的時間。 另外需要注意的是,使用者需要構建網路提供給HTN來工作。和GOAP風格的計劃系統相比,這是一柄雙刃劍。雖然這可以讓設計師完成他們想要的非常有表現力的行為,但它消除NPC的去構建那些設計師可能沒有想到事物的能力。根據你所建立的遊戲,它可能會被認為是一個優勢或弱點。

4.8 Conclusion

有了這些各種各樣的行為選擇演算法,AI程式設計師就需要知道每一種方法的知識,以在給定情況下最好的來應用。哪那種方法對於給定的NPC是最好的,可以依賴遊戲,NPC的知識狀態,目標平臺等等。雖然每個可用選項,都不是全面的對策,多瞭解一些選項從哪裡開始也是非常寶貴的。花時間去仔細考慮遊戲的需求,AI系統可以製作出最好的玩家體驗並在開發時間和易用性上保持平衡。