Behaviour Tree
概述
機器人SDK的行為樹仍然是內部測試版,這意味著它將隨著時間逐漸準備好生產,而且將在進一步的版本中改善API及效能。
這裡是一個關於行為樹的工作方式的簡介影片,以及一些我們自己的執行方式的細節:
從開始到1:15:概述;
從1:15到21:40:行為樹基本概念;
從21:40到結束:探索範例裝飾項目/分葉/服務節點的程式碼。
建立一個新的行為樹
在編輯器的頂端列上,按一下(+)按鈕,然後選擇行為樹選項。
然後將提示您儲存BT檔案。 將它儲存在您希望的地方。 這將建立一個資料資產,用於保存您 **在視覺編輯器上** 已完成的工作。注意事項:您現在選擇的名稱,將是您在編譯您在視覺編輯器上已完成的工作時,進一步生成的另一個資料資產的名稱。
這將是用於從Quantum模擬中實際驅動您的機器人的資料資產,所以您已經可以選擇一個暗示性的名稱。
當您儲存檔案時,主要機器人SDK視窗將填入一個單一節點,其為 根節點,讓您來開始您的工作。
根節點
如同名稱所提示,這是樹的起始點。它是BT代理元件上應參照的主要資產,並且定義第一個應執行的複合或分葉節點。
根節點 只能有一個下層。為了連接根節點到任何其他節點,將滑鼠游標放在根節點的底部部分之上,並且按一下顯示的「+」按鈕。這將開始連結流程。
如果您還沒有建立其他節點,您可以按一下任何空白的空間,之後將出現一個節點建立面板。
節點狀態
幾乎所有在行為樹上的節點都有其自己的狀態。這是非常重要的,因為這是行為樹的工作流程的主要定義方式。
狀態可以是:
- 成功:當節點成功地完成它應該完成的工作時。當節點傳回成功時,控制走到樹的上方到上層節點,其現在知道下層已經成功,並且可以基於該資訊來定義工作流程;
- 失敗:當節點無法執行其工作。當它發生時,當結果成功時,控制走到樹的上方只是湖;
- 運行中:如果在該特定幀上,節點在執行其工作時沒有成功或失敗,那麼節點需要運行更多幀,才能重新調整執行到其上層節點。一次只能有一個運行中的節點,因為運行中的節點被快取在BT代理上,並且是在連續的幀上重複地被執行,直到結果改變。
- 停用:只在內部使用,所以您不必擔心這個問題
當撰寫您自己的遊戲特定節點的程式碼時,您將決定您何時希望它們成功、失敗或繼續運行。這將直接地影響將選擇哪一個行為樹的分支。
我們之後將進一步說明節點的類型,以及您建立新的節點的方式。
建立新的節點
有兩種主要的方式可以建立新的節點:
- 當您以滑鼠右鍵按一下編輯器視窗上的任何空白空間,從內容選單建立
- 從一個節點初始化一個新的連結,然後按一下任何空白空間來建立
現在,讓我們看一下可以建立哪些類型的節點。
複合節點
這些是行為樹上的工作流程控制的主要來源。它們定義了可能要執行的下一個節點,而且它們的行為方式有所不同。
複合節點嘗試 從左到右執行它們的下層節點,這意味著這是 定義優先順序的方式。
一個複合節點 可以連接0個到許多個 下層節點,其可以是複合或分葉節點。
選取器節點
等價於一個 或 運算子。
- 任何它的下層成功時,它就成功。在這個時刻,停止它們的執行,而且控制走到選取器的上層節點,並帶有「成功」結果。如果還有任何下層等待被執行,在這個迭代中它們將不會被執行;
- 只有當所有下層都失敗時,它才失敗,在這種情況下,選取器傳回「失敗」結果到其上層節點。
序列節點
等價於一個 和 運算子。
- 任何它的下層失敗時,它就失敗。傳回「失敗」結果到其上層節點,並且剩下的下層這次不會被執行;
- 只有當所有下層都成功時,它才成功,在這種情況下,序列傳回「成功」結果到其上層節點。
以裝飾項目來中斷
如同節點狀態主題中所說明,如果一個分葉節點的狀態是「運行中」,這意味著特定分葉節點將被快取,並且將在下一個幀上被重新執行。但是這也意味著我們也需要基於某些情況,來使用一些方法以中斷該分葉節點的執行。舉例而言,如果您的FPS角色正在射擊一個目標,並且它突然識別到一個手榴彈正朝著她/他的方向來投擲,那麼角色將停止射擊並且尋找掩護以躲避手榴彈。
已經 檢查過的裝飾項目,將 不會 在每個幀重新檢查。但是如同上述示例所說明,我們可能希望存在一些特殊情況,其中裝飾項目應被重新執行,以能夠中斷一個分葉。
有兩個方式可以完成中斷檢查:
動態複合節點
如同您可能已經注意到的,每個複合節點有一個IsDynamic
欄位,其是一個您可以在編輯時間中切換的布林值:
如果一個複合節點是動態的,這意味著,當該特定複合是目前正在執行的子樹的一部分時,它的所有裝飾項目都將在每個幀被重新檢查。如果任何裝飾項目失敗,那麼目前的分葉節點將被中斷,並且該複合節點將成為 失敗。
這樣做的優勢是您可以選擇哪一些複合 需要 在每個刷新被重新評估,所以您可以更好地控制您最佳化您自己的樹的方式。
反應式裝飾項目
如果您有取決於黑板的裝飾項目節點,您可以讓這些裝飾項目 監看特定黑板輸入項目的改變。
這樣做的好處是,再一次地,不用在每個刷新時執行裝飾項目檢查,而是取而代之地,只檢查您是否在黑板輸入項目上設定了任何可能的新內容。
舉例而言:您有一個「比較整數裝飾項目」,其檢查一個來自黑板的整數「A」是否大於來自黑板的整數「B」。有了反應式裝飾項目,只有在您的程式碼的任何部分,您在黑板上更改輸入項目「A」或「B」時,才進行檢查。
當中斷發生時,您可以選擇您希望 中止 目前執行的方式,其可以有三種類型:
- 自身:停止目前節點的執行,並且中止所有事情,直到它到達中止節點;
- 較低優先順序:繼續執行目前節點,但是中止同層節點;
- 兩者:同時使用兩個邏輯
可以在裝飾項目節點自身上定義 中止類型:
反應式裝飾項目是在模擬程式碼上設定,所以請看一下BT 編碼章節以取得更多細節。
分葉節點
這些是在行為樹上的最低階層的節點。
它們負責執行大多數的遊戲特定邏輯,高度依賴它們運行時應傳回的狀態。
分葉節點的簡單示例有:
- 等待節點:持續運行,直到一個特定數量的時間經過。當計時器結束時傳回「成功」;
- 追擊節點:當運行時,持續將BT代理向一個目標實體移動。如果代理成功到達其目標,則傳回「成功」。如果代理被阻擋而無法到達該目標(比如,目標被摧毀,或是在一個不同的導航網格區域),則傳回「失敗」;
- 偵錯節點:在主控台上列印一個訊息,並且總是傳回「成功」;
所以您將需要傳回的狀態完全取決於您的需求。
**附註:** SDK提供的其中一個分葉節點是`WaitLeaf`。為了讓這個節點正常工作,請啟用`BotSDKTimerSystem`,因為這個被該節點使用以計算經過時間。裝飾項目節點
裝飾項目是 條件式節點。它們目的是協助定義哪一些分支應該被執行。
除了能夠傳回一個狀態,這個節點類型 也傳回一個布林值,如果布林值結果為真,其之後引導至「成功」,而如果布林值結果為假,其引導至「失敗」。
裝飾項目節點可以 阻擋或允許 其附加到的子樹的執行。它們在 複合及分葉 節點中被新增,所以您可以選擇性地表達哪些節點在被執行之前應該考量哪些情況。
裝飾項目節點的示例有:
有武器節點:如果BT代理在它的武器上有超過零個子彈,則傳回「真」;
有目標節點:如果BT代理有一個在其記憶體上(或在其黑板上)定義的目標,則傳回「真」;
冷卻節點:只有在該特定節點不在最後的「T秒」內被執行時,才傳回「真」;
所以裝飾項目應該用於協助允許或阻擋某些分支。舉例而言,「有武器節點」可用於讓「射擊」分支被執行,或阻擋它並且引領樹到「重新裝填武器」分支。
為了定義裝飾項目,按兩下任何 複合或分葉節點 然後您將被帶到其子圖表。在那裡,您將找到一個裝飾項目列表,其將以指定的順序執行。以滑鼠右鍵來建立新的裝飾項目,並且 以裝飾項目根節點來連接它們,如同下述圖片所示:
在節點的子圖表上定義的裝飾項目,可以在裝飾項目列表上在其頂部階層檢視中看見:
服務節點
大部分作為協助程式節點,其不直接影響行為樹的工作流程。這些節點通常用於更改遊戲狀態,且不需要重新調整任何事情。
服務節點是 唯一一個不會傳回一個狀態的節點類型。
如同裝飾項目,服務節點 可以被新增到複合或分葉節點。只需前往節點的子圖表,並且建立/連接它到服務根節點。
服務節點的一個重要特性是 它們按照時間的間隔被執行。每個服務節點有一個IntervalInSec
欄位,您可以在編輯時間中定義它,以定義執行服務的頻率,這相當有用,因為您將有更多機會控制效能。
附註: 為了使用服務節點,請啟用 機器人SDK計時器系統,因為這是這些節點將計時的方式;
服務節點的示例有:
- 更新目標位置節點: 隨著時間,更新代理應該前往的位置。它可以被關聯到導航網格(比如在它上面獲得一個隨機的位置),可以被關聯到追擊一個特定實體等等;
- 執行跳躍節點: 隨著時間,代理執行一個跳躍;
所以服務用作為協助工具,因為它們不需要傳回狀態,它們可以被附加到您需要的任何節點,甚至有助於解耦之類的事情。
重要注意事項是 服務被儲存為子樹的一部分被執行,這意味著如果您有一個帶有服務的選取器節點。而且您目前由於運行一個特定的分葉而「卡住」,選取器節點含有的服務 將持續執行 直到該子樹不再是目前的。所以,可以這樣說,您的整個樹取決於有一個已更新的目標位置,然後您可以新增一個UpdateTargetPosition
節點到您有的第一個選取器/序列。或是,如果只有樹的一個部分需要目標位置,那麼您可以就在那裡新增服務。所以它非常彈性。
如同裝飾項目,您可以在頂層圖表檢視上觀察服務清單:
## 編譯您的行為樹為了實際上使用您建立的BT,您需要編譯您的工作。
為了編譯,您有兩個選項:
- 左按鈕只用於編譯目前開啟的文件;
- 右按鈕用於編譯您在您的專案中有的每個AI文件。
您的BT檔案將位於:「Assets/Resources/DB/CircuitExport/BT_Assets」。
設定用於您的機器人的AI
為了最終使用建立的AI,您只需要參照已編譯的資產。
您可以基於GUID來載入資產以完成它,或是您可以只是建立一個AssetRefBTRoot
以指向所需的AI資產:
行為樹編碼
新增元件到實體
為了設定您自己的代理,新增元件BTAgent
到它,直接在一個實體原型上進行 或 透過var btAgent = new BTAgent()
來建立元件,然後以frame.Set(myEntity, btAgent)
來設定它到選取的實體,以從程式碼來新增它。
初始化BT代理
然後,在任何更適合您的應用程式的點,您必須調用:
C#
var btRootAsset = f.FindAsset<BTRoot>(btReference.Id);
BTManager.Init(f, myEntity, btRoot);
參數是:
- 幀;
- 含有BT代理元件的實體;
- 從視覺編輯器建立的BT根資產;
現在已經初始化實體的BT代理,您只需要在一個系統的更新上調用更新方法:
C#
BTManager.Update(f, myEntity);
有了這個,AI應該已經執行您在視覺編輯器上建立的工作流程。
節點編碼
總體而言,大部分的節點類型繼承於相同的層級,所以它們全部都共享非常相似的API,其可以被覆寫,以建立您自己的自訂程式碼。
在這個初始行為樹開發階段上,我們鼓勵您現在只 執行分葉、裝飾項目及服務節點,並且格外注意是否需要執行一個新的複合節點,因為這個節點類型需要更多對於BT工作方式的了解。您也可以要求新增新的複合節點,而且如果它作為通用節點是有意義的,而不是某種遊戲特定的節點,我們可以將它作為預設SDK的一部分來執行及交付。
開始您自己的裝飾項目/分葉節點
- 為了建立一個新的裝飾項目節點,建立一個繼承於
BTDecorator
的新的層級; - 為了建立一個新的分葉節點,建立一個繼承於
BTLeaf
的新的層級; - 為了建立一個新的服務節點,建立一個繼承於
BTService
的新的層級;
重要事項: 您需要將上述任何層級標記為[System.Serializable]
。
針對裝飾項目及分葉節點的API
- 只調用 一次
Init
,就是當調用BTManager.Init
的時候。它應該用來針對該特定節點的資料來配置空間。如需取得更多關於這個情況的資訊,請閱讀跟隨的 節點資料 主題; - 在造訪特定節點的時刻,並且在執行節點的更新之前調用
OnEnter
。它對於設定資料而言非常有用,比如儲存一個計時器FP。WaitLeaf
層級有一個在代理上儲存計時器資訊的示例。但是再次強調,在 節點資料 主題中對它有更好的說明; - 在每個分葉被執行的刷新時都會調用
OnUpdate
。傳回BTStatus
,這樣您可以選擇是否/何時希望傳回Success/Failure/Running
。一個非常簡單的示例,其總是導致在DebugLeaf
層級中看見Success
。WaitLeaf
層級有一個稍微複雜一點的範例,其導致Running
或Success
- 當節點完成其工作時,或是當中止它並且執行向樹的上方走時調用
OnExit
。可以在需要時用於取消初始化任何資料;
關於裝飾項目節點的考量
裝飾項目有一個額外的可以被覆寫的方法:
- 在節點的更新時調用
DryRun
。它傳回一個Boolean
,其取決於您的遊戲特定需求
當執行裝飾項目時,經常更加注意執行DryRun
方法,因為裝飾項目經常傳回Success or Failure
,其 直接 取決於您DryRun
是否傳回True or False
。所以執行OnUpdate
方法來更改這個是不常見的需要,但是它也有可能是需要的。
針對服務節點的API
- 在執行服務的時候調用
OnUpdate
,其取決於視覺編輯器上定義的間隔。
節點資料
有些節點可能需要有其自己的整數和/或FP資料,其應被新增到遊戲狀態,並且它只能被節點自己來更新。
因為這對於某些重要的節點來說是很常見的,BTAgent
元件已經有一個儲存空間來儲存這些特定節點資料。
舉例而言:
- 複合節點需要儲存目前被執行的下層索引;
WaitLeaf
節點需要儲存結束等待的時間值;
相同的方式,您可能需要有一些資料在需要在運行階段更改的節點之中。
但請記得,節點是 資料資產,所以您 在運行階段不能改變其欄位。您需要將資料儲存為幀資料,然後從那裡更改它。
針對整數及FP欄位,透過使用BTDataIndex
類型可以很輕易地完成它。這個架構是在視覺編輯器上在編譯流程中預先內嵌的,並且保證您在您的節點資產上有的每個BTDataIndex
,都將有一個獨特的索引值。
如果您識別到您的節點需要這類可變更的資料,您只需跟隨這個步驟:
- 以一個暗示性的名稱來建立
BTDataIndex
類型的一個新的欄位,這樣您就知道該索引將代表的資料。舉例而言,在WaitLeaf
程式碼上,欄位宣告是:public BTDataIndex EndTimeIndex;
,因為在運行階段時該特定節點需要讀取/寫入結束時間; - 在
Init
方法上,您將透過執行:btAgent->AddFPData()
或btAgent->AddIntData();
,來配置資料到BT代理。您將在參數中告知您希望儲存的初始值; - 為了從BT代理讀取該資料,請執行:
p.BtAgent->GetFPData(frame, EndTimeIndex.Index)
,其中編輯時間索引只是我們已經組織WaitLeaf
節點的範例; - 為了在BT代理上寫入資料,請執行:
p.BtAgent->SetFPData(frame, endTimeValue, EndTimeIndex.Index);
回應式裝飾項目編碼
如同一個前述的主題所說明,如果有一個裝飾項目觀察黑板輸入項目,可以註冊它成為一個回應式裝飾項目,這樣不論被觀察的輸入項目何時發生更改,裝飾項目將被重新檢查,可能會中止目前的執行。
從程式碼的角度, 跟隨這些步驟以使用回應式裝飾項目:
- 在一個裝飾項目層級的
OnEnter
方法上,在 每個所需的黑板輸入項目 上註冊裝飾項目,其取決於您希望觀察哪個輸入項目:
// --> Sample from BTBlackboardCompare
// We let the user define, on the Visual Editor, which Blackboard entries
// shall be observed by this Decorator
public AIBlackboardValueKey BlackboardKeyA;
public AIBlackboardValueKey BlackboardKeyB;
public override void OnEnter(BTParams p)
{
base.OnEnter(p);
// Whenever we enter this Decorator...
// We register it as a Reactive Decorator so, whenever the entries are changed,
// the DryRun is executed again, possibly aborting the current execution
p.Blackboard->RegisterReactiveDecorator(p.Frame, BlackboardKeyA.Key, this);
p.Blackboard->RegisterReactiveDecorator(p.Frame, BlackboardKeyB.Key, this);
}
- 在
OnExit
上,取消註冊裝飾項目:
// --> Sample from BTBlackboardCompare
public override void OnExit(BTParams p)
{
base.OnExit(p);
// Whenever the execution goes higher, it means that this Decorator isn't in the current subtree anymore
// So we unregister this Decorator from the Reactive list. This means that if the Blackboard entries
// get changed, this Decorator will not react anymore
p.Blackboard->UnregisterReactiveDecorator(p.Frame, BlackboardKeyA.Key, this);
p.Blackboard->UnregisterReactiveDecorator(p.Frame, BlackboardKeyB.Key, this);
}
- 不論何時您更改您希望觸發回應式裝飾項目回應的黑板輸入項目:
blackboard->Set(f, "SomeKey", someValue)->TriggerDecorators(p);
定義欄位值
如需取得更多關於設定值到動作/決策欄位時,您有的替代方法的資訊,請參見此處:定義欄位值。
AI參數
如需取得更多關於使用AI參數的資訊,如果您希望有更彈性的欄位,其可以透過不同的方式來定義的話,請參見此處:手動或從黑板/常數/設定節點來設定:AI參數。
AI內容
如需了解更多以參數傳送代理內容的資訊的方式,請參見此處:AI內容。
機器人SDK系統
有一個層級用於自動化一些流程,比如取消配置黑板記憶體。如需取得更多關於它的資訊,請參見此處:機器人SDK系統。
偵錯工具
機器人SDK附有其自己的偵錯工具。它讓開發人員能夠在運行階段選擇任何BT代理,並且看見在視覺編輯器上的醒目顯示的最近的代理的工作流程。這裡是一個偵錯工具的範例,其在機器人SDK範例專案上工作:
- 藍色 = 目前執行的子樹。藍色連結顯示了到目前為止的路徑,而最深藍的藍色節點是目前正在運行的節點;
- 綠色 = 在應用程式的該點的每個成功的子樹。請注意一個複合節點將只在其下層相應地成功時,才會成為綠色(序列需要所有成功的下層,選取器需要至少一個成功的下層);
- 紅色 = 在應用程式的該點的每個不成功的子樹;
- 灰色 = 沒有造訪的分支,並且晚一點可能造訪。
使用偵錯工具
這是一個逐步的步驟,以在您的專案上使用偵錯工具:
- 在您的
SystemSetup.cs
檔案上啟用BotSDKDebuggerSystem
。使用這個特定的系統是可選擇性的,也就是如果您希望在其他地方有偵錯邏輯,您可以 在已驗證幀,在您的自訂系統中調用BotSDKDebuggerSystem.OnVerifiedFrame?.Invoke(f);
; - 在視覺編輯器上,在頂層面板按一下偵錯圖示。當圖示成為綠色時,偵錯它將 啟用;
現在,有兩種方法來選擇將被偵錯的實體。它可以被關聯到一個選取的遊戲物件,或它可以在一個偵測器視窗上被選擇。您可以選擇上述其中一個。或兩者:
從一個遊戲物件偵錯:
選擇您的預製件/實體原型,其代表一個有著
BTAgent
作為一個元件的Quantum實體;新增
BotSDKDebugger
到它;在運行階段,開啟機器人SDK視窗,選擇已新增
BotSDKDebugger
的遊戲物件。這樣就行了!偵錯應該已經開始工作;
從一個偵錯工具偵測器視窗來偵錯:
在模擬側,您需要註冊代理實體到偵錯工具視窗。可以調用這個來完成:
BotSDKDebuggerSystem.AddToDebugger(entitiRef, (optional) customLabel)
針對已偵錯實體的顯示的預設名稱跟隨這個模式:
Entity XX | AIAssetName
。但是如果您希望提供一個自訂名稱到偵錯輸入項目,您可以使用customLabel
參數。它可以是您希望的任何名稱。也可以建立階層。只需在自訂標籤上使用分隔符號
/
,它將在偵錯工具視窗上建立階層,其可被摺疊、擴展等等;在Unity上,按一下在偵錯工具啟用按鈕右側的按鈕。它開啟一個新的視窗,其顯示所有已註冊實體。選擇一個您希望偵錯的實體,這樣就完成了。
舉例而言,一些用於上述範例GIF的自訂標籤有:Monster 1, Monster 2, Blue Team/Commander, Blue Team/Warriors/Foo, Blue Team/Warriors/Fuz and Blue Team/Wizards/Bar
重要事項: 當啟用偵錯工具,它將配置記憶體以儲存偵錯所需的資料,如果您正在 從編輯器遊玩,其 可能減慢 遊戲。所以,如果您正在從Unity之內分析應用程式,分析的一部分可能被關聯到偵錯工具,所以請考慮在分析期間停用它。
附註:偵錯工具視窗也將顯示 沒有一個實體檢視 的實體,所以這是您可以找到它以偵錯它們的BT的方式;
附註2:目前,不可以偵錯沒有連接到一個實體的代理,比如處於DSL全域的代理。這將只在進一步的版本中新增。
視覺編輯器註解
如需取得更多關於如何在視覺編輯器上建立註解的資訊,請參見此處:視覺編輯器註解。
更改編譯匯出資料夾
預設下,機器人SDK的編譯生成的資產將被放在Assets/Resources/DB/CircuitExport
資料夾之中。請參見此處以了解您如何更改匯出資料夾:更改匯出資料夾。
選擇已儲存的歷史大小
可以更改儲存在機器人SDK檔案的歷史輸入項目的數量。請參見這裡以取得更多相關的資訊:更改歷史儲存計數。
Back to top