Hierarchical Finite State Machine (HFSM)
簡介
使用狀態機,開發者可以輕鬆定義代理可能處於的各種狀態。
每個狀態基本上由以下部分組成:
- 動作:表示代理在進入/更新/退出狀態時執行的邏輯;
- 轉換:從一個狀態到其他狀態的連結,定義了代理可以轉換到的其他可能狀態。
Bot SDK 的狀態機是 階層式的 (HFSM),這意味著每個狀態也 可以 擁有一組子狀態(或下層狀態),這在狀態機的組織方式上帶來了巨大的差異。對於更複雜的 AI 行為來說,這是一個非常重要的部分,但使用階層結構並不是強制性的。
狀態機讓開發者能夠定義 AI 代理的所有可能性,因為所有的狀態、動作和轉換都是由開發者定義的。狀態機算法通常不會嘗試創建計劃或提出超出開發者設定範圍的解決方案。
目前,我們可以將其稱為一種 固定結構 的 AI 模型,因為所有內容都是固定的,並且在運行時不會創建或計劃任何內容。
優點與缺點
以下是使用 Bot SDK HFSM 時需要考慮的優點和缺點:
- 優點:
- 性能:由於其固定結構的性質和內部機制的簡單性,HFSM 非常快速。因此,大部分性能取決於用戶如何實現其特定的 AI 邏輯,例如動作和決策;
- 記憶體消耗:
HFSMAgent
元件非常簡單,只需要快取少量資料即可工作。因此,擁有大量 HFSM 代理並不會顯著增加記憶體使用量; - 易於表達:狀態、動作和轉換的概念非常容易理解。這也是狀態機在遊戲開發領域經常被提及的原因之一。無論是程式設計師還是遊戲設計師,都能快速理解其概念並立即開始開發;
- 嚴格控制:其固定結構允許用戶更精確地了解某個狀態中發生的事情以及可能的轉換。
- 缺點:
- 嚴格控制:是的,這既是優點也是缺點。需要定義所有可能性會帶來特定的維護需求,因為新增更多狀態和邏輯可能需要開發者重新審視當前的狀態並頻繁調整;
- 混亂的狀態:複雜的 HFSM 可能由於狀態和轉換的數量而變得難以理解。合理使用階層結構並新增註解對於使 AI 圖更易於理解和維護非常重要;
- 靈活性較低:有時讓 AI 自己嘗試制定計劃而不是手動定義所有內容可能是一個有趣的方法,這在 Bot SDK 的效用理論中更有可能實現;
使用 Bot SDK 時,HFSM 通常是一個有趣的方法,特別是如果它符合團隊在 AI 設計上的個人偏好。它可以用於簡單或複雜的代理,並且與其他 Bot SDK 模型相比,由於其 CPU 和記憶體效率,它是 最能擴展到大量代理 的模型。
創建狀態機文件
在 Bot SDK 窗口中,點擊New Document
按鈕並選擇Hierarchical Finite State Machine (HFSM)
:

為 AI 文件選擇一個名稱。該文件是一個可指令碼物件,僅在編輯器端需要 XML,這對 Quantum 模擬並不重要。它不需要包含在構建中。
為此 AI 文件選擇的名稱也將是用於模擬中實際更新實體 AI 的 Quantum 資產的名稱,因此最好選擇一個有意義的名稱。

創建新的 HFSM 文件後,它已經填充了一個非常基本的狀態,該狀態不執行任何操作,也沒有任何轉換:

狀態的特徵:

- 表示這是此階層級的初始狀態;
- 狀態的名稱;
- 顯示它擁有的下層狀態數量,如果有的話(預設為零);
- 折疊/展開按鈕;
- 此狀態的轉換;
- 新增更多轉換的按鈕(僅在滑鼠懸停時顯示);
創建新狀態
要創建新狀態,請在編輯器窗口的任何空白處以右鍵單擊並選擇 創建新狀態

編輯狀態
要編輯狀態,請右鍵單擊目標狀態並選擇 編輯此狀態 或選擇它並按 F2。

- 定義狀態名稱;
- 刪除轉換;
- 重新排序轉換(僅視覺上,不定義轉換的優先級)。
按 Enter 應用更改或 按 Esc 放棄更改。
折疊狀態視圖
通常,HFSM 的狀態和轉換數量會大幅增加,這可能使理解某些 HFSM 的流程變得困難。
在任何狀態節點中,單擊 折疊 按鈕以隱藏轉換槽並更改從/到它的線條繪製方式,從而啟用簡化視圖。

讓我們分析一下 之前 及 之後 的情況。
之前:

之後:

創建兩個狀態之間的轉換
要開始創建兩個狀態之間的轉換,首先單擊狀態左側/右側邊界上的任何小圓圈。
然後,單擊另一個狀態,將創建一個新的轉換。
也可以單擊空白處,編輯器將顯示節點創建面板,從中可以立即創建新狀態。

每當創建新轉換時,它將具有深色,表示轉換的條件 尚未定義。
有一些與轉換互動的方式:
- 當滑鼠游標懸停在轉換上時,它會變粗體;
- 當使用左鍵選擇轉換時,小點將沿著它移動以指示轉換的方向和目標。在此階段按 Delete 鍵可以刪除轉換;
- 雙擊轉換以進入其子圖;
- 右鍵選單中還有其他選項,例如 Mute 選項,這可能非常方便;
每個轉換子圖都有一個固定節點。讓我們來分析一下:

這是定義轉換的節點。
它有四個重要方面:
- 節點名稱,指示原始狀態和目標狀態。可以從右鍵選單中 更改名稱,從而可以設置一個有意義的名稱,該名稱出現在上層,使理解轉換的條件變得更容易;
- 定義在評估此轉換時要考慮的 事件;
- 定義 決策 或組成轉換的決策集;
- 定義從 同一節點 出發的所有轉換之間的執行順序(更多細節稍後介紹)。
讓我們為此轉換定義一個簡單的決策。
為此,右鍵單擊任何空白處,將顯示一個面板,顯示可以創建的所有可能節點。尋找。

為了簡單起見,讓我們選擇 TrueDecision,它總是返回True
(意味著應該發生轉換)。

有一個輸出槽,定義了此決策的結果將被驅動到哪裡。
左鍵單擊該槽並將其連接到決策槽:

透過此設置,每當 Bot 處於NewState
並且 HFSM 更新時,狀態機將轉換到NewState1
。
可以透過單擊其值字段來 編輯大多數槽的值。

按 Enter 應用更改或 按 Esc 放棄更改。
要導航回上層,可以使用頂部欄中的麵包屑按鈕或 按 Ecs。

或者,可以使用左側面板中的States
部分來導航狀態:

由於轉換現在已定義,它具有更亮的顏色,因此是否進行轉換取決於所選決策中編碼的條件。
當未提供決策或事件時,轉換總是進行。

定義轉換的優先級
在具有多個轉換的狀態中,可以定義哪些轉換將首先被評估。
要定義此順序,請使用轉換節點上的 優先級 槽。

可以在狀態節點上看到轉換的優先級:

轉換的評估順序是 降序(從最高優先級值到最低優先級值)。
創建新轉換
要創建新轉換,請將游標懸停在狀態的底部,(+) 按鈕將出現。單擊它。

特殊轉換類型
轉換集
這些類型的節點可用於將多個轉換分組,這對於可重用性和組織非常有用。
要創建新的轉換集,請在空白處以右鍵單擊並選擇Create New Transition Set
。請注意,此類節點也可以重命名。
這將創建一個與狀態節點非常相似的節點:它從一個未定義的轉換開始,可以從底部角落按鈕新增多個轉換:

然後,創建轉換集與其他狀態之間的連結。
這是一個示例:

也可以使用其右上角按鈕折疊轉換集:

任意轉換
這種類型的轉換可用於快速創建到目標狀態的轉換,從所有其他狀態,而不必在所有狀態上新增此轉換。
它僅考慮同一層次結構級別的狀態(層次結構將在本文檔後面解釋)。
從右鍵選單創建新的任意轉換。
現在,定義目標狀態。

在上面的示例中,該特定層次結構級別的每個狀態都將考慮任意節點的轉換。
不過,可以定義一個狀態列表,這些狀態應該 忽略 任意轉換,或者僅定義 應該 考慮它的狀態列表。
這些稱為Excluded List
或Included List
。使用菱形按鈕切換列表類型,並從+
按鈕中選擇狀態:

PS:目標節點 也 包含在任意轉換中,因此它可以使狀態轉換到自身。
門戶轉換
這種類型的轉換旨在強制 HFSM 從一個狀態轉到任何其他狀態,即使它不在同一層次結構級別上。
從右鍵選單創建新的門戶轉換,並使用下拉選單定義目標狀態:

現在,定義哪些狀態應該考慮進行門戶轉換:

PS:也可以右鍵單擊左側面板層次結構中的任何狀態,以便在當前圖表視圖中創建到該狀態的新門戶。
組合決策
除了使用單個決策來定義轉換外,還可以創建組合決策。
Bot SDK 提供了 3 個邏輯決策節點供使用。
這是一個基於 AND、OR 及 NOT 的組合決策示例:

事件
可以設置轉換,使它們可以以事件驅動的方式觸發,由名稱定義,而不一定以決策觸發。
這可能非常有用,因為它允許從 HFSM 管道外部觸發轉換,因為可以從模擬中的任何邏輯觸發事件,例如從系統邏輯中。
事件的工作方式非常簡單:每當觸發事件時,當前狀態的轉換將檢查是否正在監聽該事件。
如果任何當前轉換正在監聽該事件(從當前狀態及其層次結構中的上層狀態),則事件檢查成功。
以下是觸發 HFSM 事件的方式(從 模擬代碼 中):
C#
HFSMManager.TriggerEvent(frame, entityRef, "FooEvent");
這不僅可以新增到 Bot SDK 相關類中,還可以新增到自定義用戶系統和邏輯中。
要創建新事件,請單擊左側面板中事件部分的 (+) 按鈕:

輸入事件名稱。也可以雙擊事件以 編輯事件名稱或刪除它。
要將事件放置在轉換的子圖中,請拖放它。
然後將事件的輸出槽連接到轉換的事件槽:

注意: 與決策不同,沒有組合事件,並且轉換不接受連接多個事件。
僅由事件定義的轉換被視為有效轉換。
也可以設置 事件和決策 來定義轉換。
在這種情況下,僅當在同一幀中觸發事件並且決策條件通過時,才會進行該轉換。

定義動作
除了定義狀態機的 流程(以狀態和轉換)外,實現 AI 動作也非常重要,這些動作將使狀態機實際執行某些操作,例如更改遊戲狀態。
更多資訊請參見:定義動作
層次結構
在任何狀態的子圖中,都可以創建新的狀態和轉換集。這將在狀態之間創建父子關係。
當 HFSM 更新時,將執行當前狀態的所有層次結構。從父狀態、子狀態、孫狀態等。
這樣,就可以將子狀態機封裝在另一個狀態機中。
這在組織 HFSM 時非常有用,因為在單一層次結構中組織複雜行為可能非常困難。
例如:有兩個不同的根狀態:一個用於處理“巡邏和搜尋”邏輯,另一個用於處理“追逐和攻擊”邏輯。
每個主要狀態都可以創建許多子狀態來分別處理這些特定情況。
要創建子狀態,請進入任何狀態的子圖,並在那裡創建新狀態。
可以在左側選單中觀察到狀態的層次結構:

注意: 可以單擊這些按鈕在層次結構中導航。
重要: 還可以為 HFSM 的每個層次結構定義預設狀態。這定義了在父狀態之間轉換時進入的子狀態。要定義預設狀態,請右鍵單擊任何狀態節點並選擇“製作預設狀態”。
編譯狀態機文件
為了在模擬中實際使用 HFSM,必須在每次進行有意義的更改時編譯 AI 文件中的所有內容。
要編譯,有兩個選項:

- 左側按鈕用於僅編譯當前打開的文件;
- 右側按鈕用於編譯專案中的所有 AI 文件。
預設情況下,HFSM 文件將位於:Assets/QuantumUser/Resources/DB/CircuitExport/HFSM_Assets
。
此過程創建的主要資產類型為HFSMRoot
。

使用 HFSMRoot 資產
要使用創建的 HFSM 根資產,請使用類型為AssetRef<HFSMRoot>
的欄位來引用它,並以frame.FindAsset()
載入它。
HFSM 編碼
HFSM 有一個名為HFSMAgent
的主要元件,可以以兩種不同的方式使用:
- 將元件新增到實體中,無論是以代碼還是直接在 Unity 中的實體原型中;
- 或者在幀的全局中聲明
HFSMAgent
的實例;
最常見的用法是將元件新增到實體中。但將其與實體分離也可能有用,例如創建一個位於frame.Global
中的遊戲管理器 HFSM,並具有遊戲比賽開始、遊戲規則更新、比賽結束等邏輯。
初始化HFSMAgent
當未直接新增到實體原型中時,可以以代碼直接將HFSMAgent
元件新增到實體中。這在運行時將實體轉換為 AI 代理時非常有用,例如當玩家斷開連接時。
以下是新增元件的代碼片段(僅在未新增到實體原型時):
C#
var hfsmAgent = new HFSMAgent();
f.Set(myEntity, hfsmAgent);
HFSMManager
類有許多實用方法,這些方法是初始化和更新 HFSM 代理的主要入口點。
調用HFSMManager.Init()
以初始化代理,使其存儲其初始狀態(如編輯器中定義的)並在該狀態及其所有預設子層次結構上調用OnEnter
。
無論*是否使用 EntityPrototypes,都需要執行以下初始化步驟:
C#
var hfsmRootAsset = f.FindAsset<HFSMRoot>(hfsmRoot.Id);
HFSMManager.Init(frame, entityRef, hfsmRootAsset);
使用 "OnComponentAdded" 回調進行初始化
也可以直接在 EntityPrototype 上設置對HFSMRoot
資產的引用,並使用OnComponentAdded
信號來初始化代理。
BotSDKSystem
展示了這一點。這是一個示例:
C#
// At any system...
public unsafe class AISystem : SystemMainThread, ISignalOnComponentAdded<HFSMAgent>
{
public void OnAdded(Frame frame, EntityRef entity, HFSMAgent* component)
{
// Get the HFSMRoot from the component set on the Entity Prototype
HFSMRoot hfsmRoot = frame.FindAsset<HFSMRoot>(component->Data.Root.Id);
// Initialize
HFSMManager.Init(frame, entityRef, hfsmRoot);
}
// ...
}
更新 HFSMAgent
初始化代理後,更新它:
C#
HFSMManager.Update(frame, frame.DeltaTime, entityRef);
這將啟動整個 HFSM 機制:當前狀態將被更新,其動作將被執行,轉換將被檢查,等等。
示例系統,用於初始化和更新代理
C#
namespace Quantum
{
public unsafe class AISystem : SystemMainThread, SystemMainThreadFilter<AISystem.Filter>
{
public struct Filter
{
public EntityRef Entity;
public HFSMAgent* HFSMAgent;
}
public void OnAdded(Frame f, EntityRef entity, HFSMAgent* component)
{
HFSMRoot hfsmRoot = f.FindAsset<HFSMRoot>(component->Data.Root.Id);
HFSMManager.Init(f, entity, hfsmRoot);
}
public override void Update(Frame frame, ref Filter filter)
{
HFSMManager.Update(frame, frame.DeltaTime, filter.Entity);
}
}
}
編碼動作和決策
要創建新的 AI 動作,請按照以下說明操作:編碼動作
創建新的 HFSM 決策 的方式非常相似。
創建一個繼承自HFSMDecision
的類,並覆蓋Decide
方法,根據應使決策通過或失敗的條件返回true
或 false
。
重要:始終使用[System.Serializable]
屬性標記AIAction
和HFSMDecision
類。
以下是 SDK 中提供的最基本的 HFSM 決策示例:
C#
namespace Quantum
{
[System.Serializable]
public partial class TrueDecision : HFSMDecision
{
public override unsafe bool Decide(Frame frame, EntityRef entity)
{
return true;
}
}
}
定義欄位值
請參見此處有關設置節點欄位值的替代方案的更多資訊:定義欄位值。
AIParam
請參見此處有關使用AIParam
類型的更多資訊,該類型對於具有可以以不同方式定義的靈活欄位常有用:手動設置或從Blackboard/Constant/Config Nodes:AIParam。
AIContext
請參見此處有關如何傳遞代理上下文資訊作為參數的更多資訊:AIContext。
BotSDKSystem
有一個類用於自動化某些過程,例如在元件新增和刪除回調時初始化和釋放 Bot SDK 元件的記憶體。請參見此處有關它的更多資訊:BotSDKSystem。
調試器
Bot SDK 附帶了自己的調試工具。它使開發者能夠在運行時選擇任何HFSMAgent
,並在視覺編輯器中查看代理的最新流程。以下是 Bot SDK 示例專案中調試工具的示例:

如上圖所示,可以看到代理當前所在的狀態,以及導致該狀態的最新三個轉換。藍色轉換是最新的轉換。它比之前還有更多的圓圈穿過線條,而之前的轉換則為黑色。
此外,還可以在層次結構視圖中檢查當前狀態。帶有箭頭的狀態表示 HFSM 當前處於該狀態。這對於快速檢查當前狀態非常有用,而無需在節點中找到它。

使用調試器
以下是使用調試器的必要步驟:
- 在系統配置文件中啟用
BotSDKDebuggerSystem
。使用此特定系統是可選的,因為可以從用戶自定義邏輯中調用相同的 API:在驗證的幀中調用BotSDKDebuggerSystemCallbacks.OnVerifiedFrame?.Invoke(frame);
; - 在視覺編輯器中,單擊頂部面板上的小蟲圖標。當圖標為綠色時,調試器處於 活躍 狀態;

有兩種方法可以選擇要調試的實體:
使用遊戲物件進行調試:
- 選擇代表具有
HFSMAgent
元件的 Quantum 實體的預製件/實體原型; - 向其新增
BotSDKDebugger
Unity 元件; - 在運行時,打開 Bot SDK 窗口並啟用調試器,選擇具有
BotSDKDebugger
的遊戲物件。調試應該已經開始工作;
使用調試器窗口進行調試:
在模擬端,將代理實體註冊到調試器窗口。可以調用以下來完成:
BotSDKDebuggerSystem.AddToDebugger(frame, collectorEntity, hfsmAgent, (optional) customLabel);
調試實體的預設名稱遵循以下模式:
Entity XX | AI Document Name
。但可以使用customLabel
參數分配特定標籤。還可以創建命名層次結構。在自定義標籤中使用分隔符
/
,它將在調試器窗口中創建層次結構,可以折疊和展開它;在 Unity 中,單擊調試器啟用按鈕旁邊的按鈕。它將打開一個新窗口,顯示所有註冊的代理。選擇要調試的代理。


重要: 當啟用調試器時,它會增加記憶體和 CPU 使用量以處理代理資料。
這可能會降低遊戲性能,因此請確保在性能測試期間始終 禁用調試器,並在調試代理行為時使用它。即使未主動使用調試器,它仍然在後台處理資料。
PS:目前,無法調試未鏈接到實體的代理,例如位於 DSL 全局中的代理。
靜音
在測試 AI 時,靜音節點以暫時禁用某些邏輯可能非常有用。請參見此處有關如何靜音節點的更多資訊:靜音
視覺編輯器註解
請參見此處有關如何在視覺編輯器中創建註解的更多資訊:視覺編輯器註解。
更改編譯匯出文件夾
預設情況下,Bot SDK 編譯過程生成的資產將放置在文件夾Assets/Resources/DB/CircuitExport
中。請參見此處如何更改匯出文件夾:更改匯出文件夾。
幀中發生的事情
在 Bot SDK 中,主要入口點是:
HFSMManager.Init
,用於初始化代理並使其運行預設狀態的初始動作;HFSMManager.Update
,應不斷調用以更新代理;HFSMManager.TriggerEvent
,強制檢查特定於事件鍵的轉換。
為了更好地可視化執行這些方法時幀中發生的事情,這裡有一個流程圖:
