谈一谈游戏AI - 行为树
鄭重說明:本文適合對游戲開發感興趣的小白初學者,本人力圖將事物用簡單的語言表達清楚,但水平有限,能力一般,文章如有錯漏之處,還望批評指正。
在本系列的前幾篇中,我們談到了談一談游戲AI - 綜述 以及 談一談游戲AI - 狀態機。今天我們來繼續聊一下另一種高級決策模型:行為樹。
一、為什么需要行為樹
有一種很重要的認識事物的思維模型就是 why-what-how 模型,因此首先我們就要了解一下 why 的問題,看看為什么需要行為樹這種高級決策模型。
在前面介紹狀態機時,分層有限狀態機(HFSM) 讓我們能夠以相對直觀的方式,構建相對復雜的行為集。
但是這個設計中存在一個小問題:
以狀態轉換規則構成的決策系統與當前狀態強綁定在一起,這是一些游戲所需要的。
仔細使用狀態層次結構可以減少重復轉換的數量,但有時候,我們的需求不一樣,可能無論處于哪個狀態或幾乎在所有狀態,你的規則都要適用。打個比方,如果 AI對象(agent)的生命值降低到25%時,則無論其當前處于戰斗狀態,空閑狀態、談話狀態或者其它任何狀態,你可能都希望他逃跑。如果用狀態機,你需要在每個狀態里都做這樣的判斷邏輯。將來新增每個狀態時,都一定把這個條件添加進去。
針對這種情況,一個理想的系統應該將決策過程和狀態本身分離,這樣你只需要在一個位置進行更改,仍然能夠正確轉換狀態,這就是行為樹的應用場景。
二、決策的行為樹表示
行為樹定義
在之前的講述游戲AI決策綜述的文章中,我們提到過一個球拍擊球的小游戲AI,用代碼 hardcode 的偽代碼如下:
every frame/update while the game is running:if the ball is to the left of the paddle:move the paddle leftelse if the ball is to the right of the paddle:move the paddle right其實這用一種稱為決策樹的圖表示起來更為直觀:
在每次決策循環執行時,只需要從最上面的 Start 開始遍歷整棵樹,遇到菱形的節點,判斷其條件,然后選擇對應的分支,直到葉子節點。
而行為樹是節點更豐富功能更強大的決策樹:
黃色的裝飾器節點,也稱為條件節點,對應 hardcode 偽代碼中的 if 條件
藍色的組合節點,可以是 Sequence 順序執行節點和 Selector 選擇節點,這也類似于程序邏輯中的與、或關系
綠色的行為節點,是AI邏輯中需要具體實現的比較獨立的業務邏輯,如尋求幫助、巡邏、逃跑、攻擊等
行為樹的構建
由于行為樹本身比較復雜,也由于組成樹的節點類型比較固定,往往利用圖形化的行為樹編輯器來構建一整棵行為樹,例如UE中的編輯器:
行為樹編輯器在使用時,導出形式可以有多種,可以是生成對應的程序代碼;也可以采取數據驅動的方式,將關鍵信息數據化,然后放入行為樹執行引擎來執行。
三、行為樹的實現形式
實現行為樹有幾種不同的方式,但是本質上是相同的,而且與前面提到的決策樹非常相似:從“根節點”開始運行,樹的節點用來表示決策或行動。但還有一些關鍵區別:
節點的返回值有三種:成功(工作已完成),失敗(無法運行)或正在運行(仍在運行并且尚未完全成功或失敗);
采取行動的節點將返回正在運行,表示行動正在進行;
之所以有正在運行這種返回結果,是因為很多邏輯在一次決策循環中是無法執行完成的,可能跨越很多個決策循環周期。因此在遍歷這棵樹時,繼續上次的節點邏輯。
還有一種行為樹的形式是前面一種形式的變形。筆者之前所在的項目使用過以下編輯形式:
可以看到這種方式沒有所謂的選擇節點,其條件邏輯和行為邏輯是并行的關系,在考察每個節點的時候,會考慮其對應的條件組是否滿足,條件組本身又可以包括與、或的嵌套邏輯組合。
四、行為樹的特點
通過前面的介紹,我們可以總結出行為樹的幾個優點:
常用于比較復雜的邏輯
比較直觀,邏輯很清晰
無狀態,每次都從root開始,一幀運行不需要知道上一幀是什么狀態
樹的分支之間互不關心、互不影響,只有樹形的父子層級關系,子節點之間互相沒有聯系。因此很靈活,新增、擴展、刪除分支都不影響其他的,很方便。而對比來看,狀態機互相之間是需要知道彼此的存在的,還有轉換,經常耦合在一起
程序可以和策劃一起配合使用。
它也有很明顯的缺點:
每次執行都要遍歷整棵樹,效率是個大問題,執行時間比狀態機要長很多
無狀態,相當于沒有記憶
關于沒有記憶帶來的問題,舉個例子來說明:
一個農民(NPC AI)要收割作物,敵人出現了,農民逃跑,逃出了距離敵人的一定范圍之后,又回去收割作物,走到敵人的范圍又逃出,這樣來回往復,是一個bug,可以根據情況來寫代碼避免,但解決方式不夠優雅。
五、解決行為樹的問題
上下文保存
很顯然,狀態機需要保存上下文來解決 AI 行為樹沒有記憶的問題。
自定義狀態
很多游戲有自己自定義的狀態保存結構,一個例子如:
struct AI_RUNTIME_DATA {// 定時 timer// 通用信息,如動態思考間隔、自定義tag標記// 移動相關信息,如坐標、速度、移動技能等// 追擊目標,射擊信息等 }黑板
更通用一些的做法是一種叫做 “黑板” 的做法,即不關心狀態數據的組織形式,統一以key-value 的形式來保存數據,這有點類似于數據存儲領域 NoSQL 的做法:以Key區分不同的上下文數據,value為用戶自定義的二進制數據,這樣使得數據的保存通用化。
黑板還可能分為本地黑板或者全局黑板。
本地黑板:作用域限定為特定的狀態機/行為樹層級內部
全局黑板:作用域為整個游戲邏輯內
UE4中就內置了黑板的功能,方便行為樹使用時候的狀態保存。
效率優化
另一個需要解決的問題就是行為樹的執行效率問題。
降低更新頻率
對于大量AI運行的情況,例如服務器中存在的大量NPC,如果需要每幀都去做一次 Sense/Think/Act 決策行為,對應到行為樹上就是從 Root 節點開始掃描整棵行為樹,這勢必帶來大量的性能消耗。
如果不是每幀都去遍歷,而是用定時器,定時思考是一種可以考慮的優化方式,其本質是降低行為樹的更新決策頻率。
定時思考的問題是不夠實時,因此也要同時添加對外部事件的響應,否則AI就會顯得很傻。
例如 FF15 中添加事件響應:
行為樹中使用Active Selector節點,當指定的事件響應(OnMessage)時,打斷當前行為并且重置狀態
行為樹與狀態機組合使用
行為樹本身遍歷是非常耗時的,而狀態機則效率較高,因此我們可以將行為樹結合狀態機一起使用:
只有最復雜的部分使用行為樹,其他情況下使用狀態機保持住當前狀態,而不必用行為樹頻繁選擇分支。
例如 FF15 中的一個示例:
上圖只有最復雜的戰斗邏輯使用了行為樹,其他情況下可以嵌套狀態機來使用。
代碼實現優化
樹這種數據結構在實現時,往往使用指針來表示父子節點之間的關系,這對于 CPU 在執行時是不夠友好的。
可以考慮將緩存不友好的樹遍歷,改造成緩存友好的數組遍歷,以空間換時間來提高執行效率。
小結
在本文,我們談了行為樹的多個方面,包括:
引入行為樹的原因
行為樹的定義、構建以及具體的實現形式
行為樹的特點以及解決行為樹問題的幾個思路
總結
以上是生活随笔為你收集整理的谈一谈游戏AI - 行为树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机使用linux教程,通过手机访问Li
- 下一篇: nodejs mysql 耗硬盘_nod