1. 程式人生 > >行為樹(Behavior Tree)實踐(進一步的討論)

行為樹(Behavior Tree)實踐(進一步的討論)

本文轉自:http://www.aisharing.com/archives/99

一. 關於選擇節點的討論

我們說過選擇節點的定義是通過判斷子節點的前提條件來選擇一個節點執行,這就牽涉到判斷順序的問題,是自左向右,還是隨機選擇,或者其他的一些規則等等,這樣就延伸出各種各樣的選擇節點。

帶優先順序的選擇節點(Priority Selector)

這種選擇節點每次都是自左向右依次選擇,當發現找到一個可執行的子節點後就停止搜尋後續子節點。這樣的選擇方式,就存在一個優先順序的問題,也就是說最左邊的節點優先順序最高,因為它是被最先判斷的。對於這種選擇節點來說,它的子節點的前提設定,必須是“從窄到寬”的方式,否則後續節點都會發生“餓死”的情況,也就是說永遠不會被執行到,為了更清楚的說明,看下面第一張圖,這三個子節點在一個帶優先順序的選擇節點下,它們的前提會被依次判斷,可以看到這個三個子節點的前提從左向右,一個比一個更嚴格,如果我們現在a為9,按照下圖的定義會執行第一個子節點,如果a為7,則會執行第二個子節點,如果a=11,則會執行第三個子節點。下面的第二張圖演示了一種節點“餓死”(Starvation)的情況,我們看到第一個子節點的前提,比第二個子節點更寬泛,只要a<10,那自左向右判斷的話,永遠會進第一個節點,所以,如果要用到帶優先順序的選擇節點,則必須檢查每一個子節點的前提,以防止節點餓死的情況.
在這裡插入圖片描述


在這裡插入圖片描述

不帶優先順序的選擇節點(Non-priority Selector)

這種選擇節點的選擇順序是從上一個執行過的子節點開始選擇,如果前提滿足,則繼續執行此節點,如果條件不滿足,則從此節點開始,依次判斷每一個子節點的前提,當找到一個滿足條件的子節點後,則執行該節點。這種方式,是基於一種稱之為“持續性”的假設,因為在遊戲中,一個行為一般不會在一幀裡結束,而是會持續一段時間,所以有時為了優化的目的,我們可以優先判斷上一個執行的節點,當其條件不滿足時,再尋找下一個可執行的節點。這種尋找方式不存在哪個節點優先判斷的問題,所以對於前提的設定的要求,就是要保證“互斥”(Exclusion)。如果我們用上面第一張圖來說明,如果我們把控制節點換成不帶優先順序的選擇節點,可以看到,當a=3時,第二個子節點會被執行,下一次當a變成9時,由於不是從頭依次判斷前提的,所以,我們還是會選擇第二個節點,而不是我們可能期望的第一個節點。正確的做法見下圖,注意每一個子節點的前提是“互斥的”。所以對於不帶優先順序的選擇節點,它子節點的排列順序就不是那麼重要了,可以任意排列。
在這裡插入圖片描述

帶權值的選擇節點(Weighted Selector)

對於這種選擇節點,我們會預先為每一個分支標註一個“權值”(Weight Value),然後當我們選擇的時候,採用隨機選擇的方式來選,隨機時會參考權值,並且保證已經被測試過的節點的不會再被測試,直到有一個節點的前提被滿足,或者測試完所有的節點。帶權值的選擇節點對於子節點前提由於隨機的存在,所以子節點的前提可以任意,而不會發生“餓死”的情況,一般來說,我們通常會把所以子節點的前提設為相同,以更好的表現出權值帶來的概率上的效果。當所有子節點的權值一樣時,這種選擇節點就成為了隨機選擇節點(Random Selector),帶權值的選擇節點對於需要豐富AI行為的地方,非常適用,比如養成類遊戲中,小狗表示開心的時候,可能會有各種各樣的表現,我們就可以用這種選擇節點,新增各種子節點行為來實現。
在這裡插入圖片描述


這些就是常用的選擇節點型別,我們可以根據需要,定義更多的選擇節點的選擇行為,其實我們可以看到,不同的選擇行為對於子節點前提的要求會有略微的不同,這是在我們搭建行為樹的時候需要注意的地方。

二. 關於並行節點結束條件的討論

我們每個節點都會有一個執行狀態,來表示當前行為是否結束。對於控制節點來說,它的執行狀態就是其子節點的執行狀態,選擇節點和序列節點比較好處理,因為對於這兩種控制節點來說,每時刻,只會有一個子節點在執行,只要返回在執行的這個子節點的狀態即可。但對於並行節點來說,它同時刻會有多個子節點執行,那我們如何來處理並行節點的執行狀態問題呢?
一般有兩種:

  1. 與:只有所有的子節點都執行結束,才返回結束。
  2. 或:只要有一個子節點執行結束,就返回結束。

為什麼要需要有節點的執行狀態呢?

  1. 序列控制節點中,需要用執行狀態來控制序列的執行
  2. 外部世界需要了解行為的執行狀態,來決定是否要更新決策(如果行為樹在決策層)/請求(如果行為樹在行為層),關於AI分層,請參考這裡

對於第二點,可以舉個例子,比如我們有一個行為是“走到A點”,假設這個行為是不可被打斷的,那當我們在走向A點的過程中,行為樹的執行狀態就是“正在執行”,當到達A點時,行為樹就返回“已完成”,這樣,對外部來說,當我們看到行為樹是“正在執行”的時候,我們就不需要做任何新的行為(為了優化,或者為了行為抖動等等),當看到“已完成”的時候,我們就可以做新的決策或者行為了。這樣一個執行狀態還有助於我們檢測行為樹的狀態,幫助除錯。

三.關於具體實現的討論

行為樹的實現可以有多種多樣,我這邊提出一些建議,一般來說,行為樹每個節點需要有進入(Enter),離開(Exit),執行(Execute)等部分,需要有行為節點(ActionNode),控制節點(ControlNode),前提(Precondition)等基類,然後,還需要定義行為樹的輸入(InputParam)和輸出(OutputParam),一般來說,我們希望行為樹是一個黑盒,也就是說,它僅依賴於預定義的輸入。輸入可以是黑板(Blackboard),工作池(Working Memory)等等資料結構,輸出可以是請求(Request),或者其他自定義的資料結構,如下圖:
在這裡插入圖片描述

四.關於繪製和除錯的討論

看到行為樹的定義後,作為程式設計師的直覺,我們很自然的就會想到,這好像應該能做一個工具來輔助行為樹的建立和除錯,我們可以把預定義好的前提和節點,在一個視覺化的編輯器裡搭建成行為樹,然後再匯出成資料給遊戲用。對於除錯來說,我們可以讓工具和遊戲通訊,然後實時的檢測行為樹的執行狀況,比如當前在哪個分支中等等。由於行為樹的邏輯是可見的,並且是靜態的,所以我們看其選擇的路徑,我們就可以知道AI為什麼會作出這樣的決策了。當我剛接觸到行為樹的時候,就在想做這樣一個編輯器,但迫於專案壓力,一直沒有時間做(工作量還是挺大的),有興趣,有時間的朋友,可以考慮做一個。順便說一句,我現在對於行為樹的搭建都是在程式碼中完成的,雖然沒有資料驅動那麼“先進”,但通過巨集定義,排版等方式,還是能非常清晰的表示樹的整體結構。

關於行為樹,我想這個系列就到這裡了。在使用行為樹的過程中,可能還會碰到這樣和那樣的問題,包括我自己在實踐中的一些經驗,我想就先不包括在這個系列裡了,以後再單獨拿出來聊,這個系列作為行為樹的入門,希望對大家有所幫助,歡迎指教和討論。



更多案例請關注“思享會Club”公眾號或者關注思享會部落格:http://gkhelp.cn/


在這裡插入圖片描述