Hierarchical Finite State Machine
建立一個新的HFSM
在編輯器的頂端列上,按一下(+)按鈕,然後選擇狀態機選項。
之後將提示您儲存HFSM檔案。
在您希望的地方儲存它。
這將建立一個資料資產,用於保留您 在視覺編輯器上 已完成的工作。
注意事項:您現在選擇的名稱,將是您編譯您在視覺編輯器上已完成的工作時,進一步生成的另一個資料資產的名稱。
那將是實際上用於驅動您的機器人的資料資產,所以您已經可以選擇一個暗示性的名稱。
當您儲存檔案時,主要的機器人SDK視窗將被一個單一的 新狀態 填入,供您開始使用。
現在,讓我們進一步看看這個初始狀態:
- 指出這是HFSM的初始狀態;
- 狀態的名稱;
- 從這個狀態而生的轉換清單。
建立一個新的狀態
為了建立一個新的狀態,以右鍵按一下在編輯器視窗上的任何空的空間,並且選擇 建立新狀態
編輯一個狀態
為了編輯一個狀態,以右鍵按一下目標狀態,並且選擇 編輯這個狀態。
在這裡,您可以定義狀態的名稱及刪除/重新排序轉換。
附註:重新排序轉換將不會改變它們的優先順序。 它只用於視覺調整。在這個教學中將進一步說明優先順序主題。
它只用於視覺調整。
按下Enter 以適用更改或 按下Esc 以放棄那些更改。
最小化狀態檢視
常見的情況是,HFSM在狀態及轉換的數量方面大幅增加,這可能導致難以實際了解某些HFSM的流程。
在任何狀態節點上,您可以按一下 最小化 按鈕,以隱藏轉換槽及更改從它開始畫/畫向它的線的方式,從而啟用一個簡化的檢視。
讓我們分析 使用前 及 使用後。
在兩個狀態間建立一個轉換
為了開始在兩個狀態間建立一個轉換,首先按一下任何狀態的左/右邊緣的小圓圈的任一個。
然後,如果您按一下另一個狀態,將建立一個連接這兩個狀態的新的轉換。
如果您不是按一下另一個狀態,而是按一下任何空白的空的空間,如果您希望立即建立目標狀態,編輯器將彈出建立面板。
無論您何時建立一個新的轉換,它將是暗色,指出該轉換 還沒被定義。
有幾種與轉換互動的方式:
- 當您的滑鼠滑過一個轉換,它將被醒目提示;
- 當您以左鍵按一下一個轉換,小圓圈將走過它,以指出轉換的方向;
- 當您按兩下一個轉換,您將進入它的子圖表;
- 從右鍵選單中,您可以:進入轉換子圖表,停用轉換或刪除它。
讓我們來分析已經在決策子圖表上建立的節點:
這是用於實際地定義轉換的節點。
這裡有四個重要的概念:
- 這個節點的名稱指出原始狀態及目標狀態。它可以被更改,這是使用右鍵選單完成,所以您可以選擇有意義的名稱,並且它將顯示在上層,這讓情況更容易理解;
- 第一個槽用於定義一個在評估這個轉換時需要被考量的 事件;
- 第二個槽用於定義撰寫轉換的 決策;
- 第三個槽用於定義來自 相同的節點 的所有轉換之間的執行的順序。
讓我們開始針對這個轉換來定義一個簡單的決策。
為了做到這點,以右鍵按一下任何空白空間,此時一個面板將會顯示您可以建立的決策。
為了簡單說明,讓我們選擇 真決策。
當然,這個決策的結果總是為真。
有一個輸出槽,其定義這個決策的結果將被驅動到的地方。
所以,以左鍵按一下 結果 槽一側上的圓圈,並且連接它到決策槽:
這樣就完成了。
您已經定義了最簡單的轉換。
有了這個,只要您的機器人處於 新狀態 並且已更新HFSM,機器人將轉換到 新狀態1。
按一下決策欄位,可以編輯一個決策欄位值。
按下 Enter 以適用更改或 按下 Esc 以放棄更改。
現在我們定義了一個非常簡單的轉換,讓我們回到頂層檢視,並且看看它看起來如何。
您可以按一下頂層列的階層連結列的 根 按鈕,來導航回狀態檢視:
或者,您可以使用左側面板,來導航狀態:
現在您可以看到轉換不再是紅色。
這指出這是一個有效的轉換。
定義轉換優先順序
對於具有多個轉換的狀態,可以定義首先要評估哪個轉換。
為了定義這個順序,使用 優先順序 槽。按一下它以設定其值。
可以在狀態節點上看見轉換的優先順序。
轉換將被評估的順序是:從高優先順序到低優先順序。
有了這個,您已經可以定義首先需要檢查哪個轉換。
注意事項: 針對狀態的轉換所定義的優先順序,對另一個狀態的轉換而言沒有影響。
建立新的轉換
我們目前只有在我們的兩個狀態之間的一個轉換。
有了這個,優先順序欄位是無用的,因為沒有狀態有多個轉換。
為了建立一個新的轉換,將滑鼠放在您的狀態的底部部分,並且按一下顯示的 (+) 按鈕。
之後您可以定義該新的轉換,就像您對第一個轉換所做的一樣。
特殊轉換類型
轉換集
這些節點類型可用於群組許多的轉換。
重新使用一個轉換集特別有用,因為許多狀態可以指向一個單一的轉換集。
為了建立一個新的轉換集,以右鍵按一下任何空的空間,並且選擇 建立新的轉換集。
這將建立一個節點,其非常相似於狀態節點:它開始於一個單一的未定義的轉換,並且您可以透過將滑鼠暫留在其底部部分,來建立許多新的轉換:
之後,只需建立連結。
這裡是一個示例:
也可以使用轉換集的右上角按鈕,來最小化轉換集:
任何轉換
當您希望定義一些應該 從位於該同一階層層級上的任何狀態 選取的轉換時,這些轉換非常有用。
附註: 將在本文檔中進一步說明階層。
從右鍵選單來建立新的「任何」轉換。
現在,定義「任何」轉換的目標狀態。
在上述的範例圖,該階層層級上的每個狀態都將考量來自「任何」節點的轉換。
但是可以定義一個忽略「任何」轉換的狀態的清單。從 已排除清單 選單中選擇這些。
入口轉換
這個轉換類型意味著強迫HFSM從一個狀態轉到 您的階層的任何層級 中的任何其他狀態。
從右鍵選單建立新的入口轉換。
現在您必須定義這個入口將把HFSM帶往哪個狀態。
按一下下拉式選單以選擇它。
請注意,目標狀態的名稱將基於您擁有的階層:
在您定義入口的目標之後,您只需定義哪些狀態將考量該入口。
已撰寫的決策
除了使用單一決策來定義轉換,也可以建立一些已撰寫的決策。
機器人SDK已經帶有3個邏輯決策可以立即使用。
這裡是一些已撰寫的決策的範例,它們基於 和、或 及 非 決策:
事件
如果您希望評估一個轉換,且不在您的 quantum_code 專案上建立一個新的決策、編譯或設定決策等等,您可以使用事件。
事件的工作方式非常簡單:每當事件被觸發時,目前狀態的轉換將檢查該事件是否正在被接聽。
如果任何轉換接聽該事件,它將被選取。
就程式碼而言,這是觸發事件所需的:
在任何 quantum_code 指令碼中,請執行:
C#
HFSMManager.TriggerEvent(f, &guy->Fields.HFSMAgent.Data, (Entity*)guy, "SomeEventName");
這個方法調用可以被新增到您的動作/決策,或到您有的任何其他邏輯,比如在您的系統中。
為了建立一個新的事件,按一下事件的工作階段的左側面板上的 (+) 按鈕:
當您這樣做,您將被提示來建立您的新的事件。
您也可以在事件上按兩下,來開啟選單以 編輯或刪除事件。
為了在轉換子圖表上放置一個事件,您必須拖放它。
然後,類似於您對決策所做的,您可以連接事件的輸出槽到轉換的事件輸入槽:
注意事項: 與決策有所不同,這裡沒有複合事件定義,並且一個轉換不接受超過一個事件的連接。
帶有由 僅一個事件 定義的決策的轉換,被視為 有效轉換。
也可以透過設定 一個事件及一個決策,來定義一個轉換。
在這種情況下,只有在同一個幀上觸發事件並且決策條件為真時,才發生轉換。
定義動作
現在我們討論了如何建立狀態機的流程(帶有狀態及轉換),讓我們看一下如何針對一個狀態來定義動作。
更多關於這個主題的資訊,請參見:定義動作
停用
當測試您的AI時,為了暫時地停用一些邏輯而停用一些節點,可能有用。更多關於如何停用節點的資訊,請參見:停用
階層
在任何狀態的子圖表上,您可以建立新的狀態及轉換的集。
假設您在 新的狀態 子圖標上,而且您建立一個新的狀態稱為 另一個狀態。
當您這樣做的時候,將在這兩個動作之間建立一個關係: 新的狀態是上層而另一個狀態是下層。
上層 及 下層狀態的動作集轉換都將被執行。
這樣的話,可以封裝一個狀態機到另一個狀態機之內。
假設如果您有 3個層次的行為的魔王,這可能非常方便:它一開始是簡易模式,然後當它的血量剩下一半時變得稍微難一點,最後在它總健康值只剩10%時變得更難。
您可以在頂層圖表建立3個主要狀態,並且各個狀態將有其自己的狀態機以定義魔王的難度。
改善HFSM的組織方式是非常有用的。
為了建立一個下層狀態,只需前往任何狀態的子圖表,以右鍵按一下空的空間,並且選擇 建立新的狀態 選項。
有了這個,您可以有一個複雜的階層層級,您可以在左側選單中看見它:
注意事項: 您可以按一下這些按鈕來導航於階層上。
也可以以右鍵按一下任何這些狀態,以在您的目前的圖表檢視上來對它建立一個新的入口。
重要事項: 也可以為HFSM上的每個階層層級來定義預設狀態。這定義了在上層狀態之間轉換時輸入的下層狀態。為了定義哪個是預設狀態,以右鍵按一下任何狀態節點並且選擇「設為預設狀態」。
編譯您的HFSM
為了實際上使用您建立的HFSM,您必須編譯您所完成的事情。
為了編譯,您有兩個選項:
- 左按鈕只用於編譯目前開啟的文件;
- 右按鈕用於編譯在您的專案上您有的每個AI文件。
您的HFSM檔案將位於:"Assets/Resources/DB/CircuitExport/HFSM_Assets"。
設定將被您的機器人使用的AI
為了最終使用建立的AI,您只需參照已編譯的資產。
為了完成它,您可以基於GUID來載入資產,或您可以只是建立一個 資產連結 以指向所需的AI資產:
HFSM編碼
現在,就DSL而言,在建立新的HFSM代理方面有兩個主要選項:
- 您可以將HFSM代理元件新增到您的實體中;
- 您可以在您的架構或全域空間上宣告它,而無需擁有實體。
這一點很重要,因為可能讓HFSM執行超出實體的範圍的動作,比如建立遊戲流程、定義遊戲開始時執行哪些動作、定義何時遊戲將改變其目前狀態等等。
初始化代理
現在首先,舉例而言,這是您建立一個新的HFSM代理,並且新增它到您的實體的方式,前提是您沒有正在使用一個實體原型:
C#
var hfsmAgent = new HFSMAgent();
f.Set(myEntity, hfsmAgent);
然後,在任何更適合您的應用程式之處,您必須基於您從視覺編輯器編譯的HFSMRoot
資產,針對該實體來調用HFSMManager.Init
方法,。
無論 您是否正在使用實體原型,下述的初始化步驟都需要完成:
C#
var hfsmRootAsset = f.FindAsset<HFSMRoot>(referenceToRoot.Id);
HFSMManager.Init(frame, myEntity, hfsmRootAsset);
// Only do this if you are not using Entity Prototypes
f.Set(myEntity, hfsmAgent);
如果HFSM初始化過程中所需的任何資訊都包含在HFSM代理本身之中,您可以從實體來取得元件,以取得必要的資料:
var hfsmAgent = f.Get<HFSMAgent>(myEntity);
使用"OnComponentAdded"回調來初始化
也可以直接在實體原型上設定參照到HFSMRoot
資產,並且使用OnComponentAdded
信號以透過該資訊來初始化代理:
C#
// At any system...
public unsafe class AISystem : SystemMainThread, ISignalOnComponentAdded<HFSMAgent>
{
public void OnAdded(Frame f, EntityRef entity, HFSMAgent* component)
{
// This is how you get the HFSMRoot from the component set on the Entity Prototype
HFSMRoot hfsmRoot = f.FindAsset<HFSMRoot>(component->Data.Root.Id);
// Then we just do the initialization step
HFSMManager.Init(f, entity, hfsmRoot);
}
// ...
}
更新代理
現在,已經初始化實體的HFSM代理,您只需要在您需要的時候調用更新方法:
C#
HFSMManager.Update(f, f.DeltaTime, myEntity);
這將使HFSM執行其初始狀態的資料並且執行動作及轉換到其他狀態。
有了這個,AI應該已經執行您在視覺編輯器上建立的流程。
一個範例系統,其初始化及更新代理
C#
namespace Quantum
{
public unsafe class AISystem : SystemMainThread, ISignalOnComponentAdded<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 f)
{
var allAgents = f.Filter<HFSMAgent>();
while(allAgents.NextUnsafe(out var entity, out var agent))
{
HFSMManager.Update(f, f.DeltaTime, entity);
}
}
}
}
編碼動作及決策
為了建立您自己的動作,遵循這些指引:編碼動作
建立您自己的決策,是非常相似的。
但是,不是建立一個其繼承於AIAction
的新的層級,而是讓其繼承於HFSMDecision
抽象層級。
同時,不是必須執行Update
方法,而是必須執行Decide
方法。
使用它來傳回true
/false
,這取決於您自己的需要。
重要事項:您需要標記您的新的層級為[Serializable]
及partial
。
C#
namespace Quantum
{
[Serializable]
public partial class TrueDecision : HFSMDecision
{
public override unsafe bool Decide(Frame frame, EntityRef entity)
{
return true;
}
}
}
定義欄位值
關於設定值到動作/決策欄位時,您可使用的替代方案的更多資訊,請參見這裡:定義欄位值.
AI參數
如果您希望有更彈性的欄位,並且透過不同的方式來定義這些欄位的話,這些關於使用AI參數的資訊很有用:手動設定或從黑板/常數/設定節點來設定:AI參數.
AI內容
如需取得更多關於如何將代理內容的資訊作為參數傳送的資訊,請參見這裡:AI內容.
機器人SDK系統
這是一個層級,其用於自動執行一些流程,比如解除配置黑板的記憶體。如需取得更多關於它的資訊,請參見這裡:機器人SDK系統.
偵錯工具
機器人SDK帶有其自己的偵錯工具。它使開發人員可以在運行階段選擇任何HFSM代理,並且看見在視覺編輯器上醒目提示的最新的代理的流程。這裡是一個用於機器人SDK範例專案的偵錯工具的範例:
如同上述GIF所顯示,可以看到代理的當前狀態,以及導致該狀態的最近三次轉換。藍色轉換是最近的一次轉換。比起先前的黑色轉換,它也有更多圓圈穿過線條。
此外,也可以檢查階層檢視上的目前狀態。帶有箭頭的狀態代表HFSM目前處於該狀態。這很有幫助,因為您不需要深入查看視覺圖表來了解代理目前在階層的深度。
使用偵錯工具
這是在您的專案上使用偵錯工具的步驟:
- 在您的
SystemSetup.cs
檔案上啟用BotSDKDebuggerSystem
。使用這個特定的系統是選擇性的,因為如果您希望在其他地方使用偵錯邏輯,您只需 在已驗證幀時 在您自己的自訂系統中調用BotSDKDebuggerSystem.OnVerifiedFrame?.Invoke(f);
; - 在視覺編輯器上,在頂層面板上按一下臭蟲圖示。當圖示為綠色時,偵錯為 啟用;
現在有兩個方式來選擇將偵錯哪個實體。它可以被關聯到一個選取的遊戲物件,或它可以在一個檢查器上被選取。您可以選擇上述的其中之一。或者兩個都選:
從一個遊戲物件來偵錯:
選取您的代表一個有
HFSMAgent
作為一個元件的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之內來分析應用程式,部分分析可能被關聯到偵測工具,所以請考慮在分析時停用它。
附註:偵錯工具視窗也將顯示 沒有實體檢視 的實體,所以這就是您如何找到它來偵錯它們的HFSM的方式;
附註2:目前,不可以偵錯沒有連接到一個實體的代理,比如仰賴DSL全域的代理。這將只在以後的版本中新增。
視覺編輯器註解
如需取得更多如何在視覺編輯器上建立註解的資訊,請參見這裡:視覺編輯器註解.
更改編譯匯出資料夾
預設下,機器人SDK編譯生成的資產將放在Assets/Resources/DB/CircuitExport
資料夾。您更改匯出資料夾的方式,請參見這裡:更改編譯匯出資料夾.
選擇被儲存的歷史大小
可以更改儲存在機器人SDK檔案的歷史輸入的數量。如需取得更多資訊,請參見這裡:更改歷史儲存計數.
在幀中發生的事
在機器人SDK上,主要輸入點為:
HFSMManager.Update
,其被持續地調用,以更新您的代理;HFSMManager.Init
,其用於初始化代理;HFSMManager.TriggerEvent
,其考量一個已觸發事件以強制進行轉換檢查。
為了協助更好地視覺化當調用這些方法時,在一個幀發生的事情,這裡是一個流程圖表:
Back to top