Shared Concepts
有些概念對於超過一個的機器人SDK AI模型而言是相同的,在這裡可以找到這些概念。
定義動作
在HFSM上,每個狀態有一個子圖表在其中。在GOAP上,每個工作也有一個子圖表。可以透過按兩下狀態/工作來存取它們。
在這些子圖表上,您可以 建立動作。
當存取圖表時,您將在頂端列看見一個階層連結列,其指出您目前所在的階層的深度。
您可以使用這些按鈕,以導航到先前的階層層級。
在子圖表上已經定義了一個重要的節點:動作根節點。
在HFSM上
在這裡我們有三個需要執行的動作清單:
- 進入時 清單定義了當HFSM進入到這個狀態時,需要執行的動作;
- 更新時 清單定義了每次更新HFSM時(一般而言是每一幀時),需要執行的動作;
- 結束時 清單定義了當HFSM離開這個狀態時,需要執行的動作。
在GOAP上
在這裡我們有一個需要執行的動作清單:
- 更新時 清單定義了每次更新GOAP時(一般而言是每一幀時),需要執行的動作;
為了定義這些動作清單,按一下動作根節點的右側的箭頭,然後按一下任何動作的輸入槽,或是按一下任何空的空間,以立刻建立一個新的動作:
在這裡,一件重要的事情是,您可以定義您需要的數量的已連接動作,並且它們將在相同的幀上按順序被執行。
使用箭頭按鈕來持續連接它們:
您可以依照您想要的方式來重新排序動作。
如果您不希望刪除動作,您也不希望執行它們,您之後也可以使用分離的動作集:
請注意,您可以按一下輸入槽來定義動作欄位值。
按Enter 以應用更改,或 按Esc 以放棄這些更改。
如同我們已經看到的,編輯器已經附有一些預先定義的動作及決策。
這些是為了開發人員而建立,以作為一個開始。
在您的專案上,您將需要執行您的特定的動作及決策。
讓我們來看一下應該如何做到這點。
編碼動作
為了建立一個新的動作,您需要開啟 quantum_code 解決方案。
在 quantum.code 專案上,您可以建立繼承於AIAction
抽象層級的任何層級。
當這樣做的時候,您將需要執行Update
方法,每當您更新您的代理(HFSM或GOAP)時,都會調用它。
對於HFSM,這就是在您定義的動作清單上執行的事情:進入時、更新時 及 結束時。所以舉例而言,當進入某些HFSM狀態時,也將針對列於初始狀態上的動作,來執行這個程式碼。
重要事項:您需要標記您的新的層級為[Serializable]
及partial
。
C#
namespace Quantum
{
[Serializable]
public partial class IdleAction : AIAction
{
public override unsafe void Update(Frame f, EntityRef e)
{
// insert the action code here
}
}
}
定義欄位值
在機器人SDK上,可以在程式碼上宣告公共欄位,這樣這些欄位會顯示在視覺編輯器上。對於HFSM,可以針對動作及決策程式碼來做這個,而對於GOAP,可以針對動作。在編輯器上,使用者可以定義該欄位的值。
設定這些值的更簡單的方式是按一下欄位,並且立刻指派值。但是還是有其他選項:
- 使用黑板節點;
- 使用常數節點;
- 使用設定節點;
- 使用AI功能節點。
因為在黑板文檔中已經說明過黑板節點,讓我們深入了解常數/設定/AI功能節點。
常數面板
可以從左側面板來定義及使用常數。
在定義常數之後,可以擷取常數節點並且連接它們到動作及決策的輸入槽(在HFSM上)。一個單一的常數節點可多次作為輸入,這使得以相同的值來定義許多欄位變得更容易。
另一個關於常數的重要的角度是,在設定選單上不論何時改變其值,所有來自於該常數的節點將相應地被更新,這讓定義在HFSM的不同部分中的值,並且隨後改變這些值,變得更加容易。
使用左側面板以定義一個新的常數,在常數索引標籤上按一下(+)符號。
然後選擇其Name
、Type
、Default Value
並且儲存它。現在,有了已定義的常數,您可以拖放它到圖表檢視,並且已經可以連接它到輸入槽。
設定面板
對於許多使用相同的HFSM/GOAP的代理,可以有不同的常數值,這很有用,舉例而言,在您希望針對不同的困難度有不同機器人,而它們有相同的行為邏輯的情形。在簡易模式下的一個射擊手機器人可以有一個「2秒」的值作為其反應時間,同時在困難模式下該值為「0.5秒」。使用設定面板可以輕易地完成這件事情。
使用在左側的面板,以建立設定值,其之後被編譯為類型AIConfigAsset
的一個新的資料資產。在編譯之後,您可以在AIConfig_Assets
資料夾中找到名為<DocumentName>DefaultConfig
的資產。
這些資產之後可以用於模擬,以擷取常數值。
讓我們建立一個非常簡單的設定配置:
除了從頭建立新的設定欄位,也可以轉換一個常數成為一個設定,或是反過來。
之後,在編譯文件之後,這是結果資產:
最後,為了有來自這個資產的變量,這樣您可以定義不同的常數值,請使用Unity的專案索引標籤上的右鍵選單,並且前往Create/Quantum/Assets/AIConfig
之下。
這將建立一個非常簡單的設定資產,其將尋找另一個設定資產來作為其基礎。填入預設設定欄位,並且按一下更新設定以鏡像預設設定資產。您隨後可以按照需要來更改值。同時,如果您希望還原值為原始設定資產值,請按一下重新設定為預設。
現在,為了使用這些設定,這裡是一些替代方案:
直接從設定資產讀取它,告知設定索引鍵,並且根據設定類型來相應地擷取值:
var myBoolean = myConfig.Get("Key").Value.Boolean;
類型可以是:整數、布林值、位元組、FP、FP向量2、FP向量3及字串
或者與AI參數類型一起使用它。
舉例而言:
首先,在任何動作/決策上建立一個AI參數FP欄位。編譯它,這樣它就會在視覺編輯器上顯示;
然後,從左側面板拖放一些設定值,並且連接它到AI參數欄位;
然後編譯文件。在程式碼上,使用AI參數API來擷取值。這將透過作為參數來傳送的設定資產,來相應地擷取設定的值:
// Considering that the variable "AttackRange" is of type AIParamFP
FP rangeValue = AttackRange.Resolve(f, blackboard, myConfig);
所以,基本上,關鍵在於基於您在視覺編輯器上擁有的東西,使用設定面板,來建立預設設定資產,然後建立它的變數,並且使用資產參照來相應地連接它們到您的需求。
HFSM代理和GOAP代理已經附有一個欄位,來參照特定代理/實體的設定資產,這僅是為了方便性。所以您可以使用它來完成您自己的參照:
// The config to be set can come from any source that you prefer. Some custom asset, RuntimeConfig, RuntimePlayer...it's up to you. Just set it to the component:
hfsmAgent->Config = config;
// or
goapAgent->Config = config;
// Then, when you need to GET the Config:
hfsmAgent.GetConfig(frame);
//or
goapAgent.GetConfig(frame);
AI參數
有不同的方法來定義欄位上的值的來源。目前可以使用黑板、常數及設定來完成它。因此,也有不同的方法來讀取程式碼上的這些值。因此,如果開發人員需要將來源類型從一種更改為另一種(也就是從黑板節點改為常數節點),那麼也需要更改來源程式碼。
但是,為什麼更改來源類型是有用的呢?
- 簡單來說,可以僅僅是以手動定義值;
- 如果值在運行階段可以改變,它可以被儲存在黑板上,所以請使用一個黑板節點來定義它;
- 如果值不改變,但是您希望它來自節點,以讓圖表變得更有彈性,那麼請使用常數節點來定義它;
- 如果上述說明適用,但是您需要它在各個代理之間而有所不同,請使用一個設定節點。
AIParam
是一個類型,其被建立以協助突然改變來源的情況。但是,在學習使用它之前,讓我們快速地分析一下在讀取值時,各個程式碼之間的不同點。
如果某些欄位曾在視覺編輯器上被手動地定義,或是曾經使用一個常數節點來定義它,那麼讀取它的程式碼是簡單直白的:
C#
// In this case, the value is directly stored on the field itself, with no need for any extra code
public Int32 MyInteger;
現在,如果該值來自於一個黑板節點:
C#
// Read the value from the blackboard asset
var value = blackboardComponent->Board.GetValue("someKey");
對於一個設定節點:
C#
// Read the value from the config asset
var myBoolean = myConfig.Get("Key").Value.Boolean;
所以,為了在視覺編輯器上的值來源改變時,不需要更改程式碼,請針對您的欄位來使用AIParam
類型。其主要的特性是:
- 它有
Resolve
方法,其接收黑板及設定資產。透過了解欄位的值的來源,這個方法已經傳回正確的值,其可以是直接傳回正確的值(當欄位被手動地定義時),或是從黑板/設定傳回值。所以您可以根據需要來多次更改值來源類型,並且這將是讀取它的程式碼:
C#
public AIParamInt MyAIParam;
var value = MyAIParam.ResolveResolve(frame, blackboard, aiConfig);
- 它目前有8個可能的類型:
C#
AIParamInt, AIParamBool, AIParamByte, AIParamFP, AIParamFPVector2, AIParamFPVector3, AIParamString, AIParamEntityRef
- 內部而言,它已經檢查在視覺編輯器上定義AI參數的方式。它是否是手動定義,或是來自任何特製化節點。
AI功能節點
有了AI功能節點,可以預先定義各種類型的「取得器」節點。這樣做的主要目的是,可以建立特定節點,其將根據您的遊戲特定需要來傳回值。
針對AI功能節點的基礎類型是:
- AI功能位元組;
- AI功能布林值;
- AI功能整數;
- AI功能FP;
- AI功能FP向量2;
- AI功能FP向量3;
- AI功能實體參照
為了建立您自己的AI功能節點,只需繼承上述任何層級,並且執行抽象Exectue()
方法。這裡是一個範例AI功能節點,其將傳回某些儲存在一個自訂元件中的實體的位置:
C#
namespace Quantum
{
[System.Serializable]
public unsafe class GetEntityPosition : AIFunctionFPVector3
{
public override FPVector3 Execute(Frame frame, EntityRef entity = default)
{
MyComponent myComponent = frame.Unsafe.GetPointer<MyComponent>(entity);
Transform3D* targetTransform = frame.Unsafe.GetPointer<Transform3D>(myComponent->TargetEntity);
return targetTransform->Position;
}
}
}
當您編譯Quantum解決方案時,AI功能現在將在內容選單中可用。也可以在AI功能層級上宣告公共欄位,您隨後可以在視覺編輯器中直接填入該欄位。
對於連接AI功能節點,必須使用上面解釋的AIParam
類型來完成。所以,如果我有一個HFSM動作,其需要基於上述的AI功能層級來取得一個實體的位置,則需要一個AIParamFP
欄位:
namespace Quantum
{
[System.Serializable]
public unsafe partial class SampleAction : AIAction
{
public AIParamFP TargetPosition;
public override void Update(Frame f, EntityRef e)
{
// If you are not sure if your AIParam's source is a Blackboard/Config/AIFunction node, then use the general Resolve method
var position = TargetPosition.Resolve(/*args*/);
// If you are sure that the source is an AIFunction node, then you can use the specific Resolve method
var position = TargetPosition.ResolveFunction(frame, entity);
// Now, do something with the position
}
}
}
正如上述示例所顯示,您可以使用一般性解析方法,其取決於在視覺編輯器(黑板、設定、AI功能節點)上定義的來源,而傳回一個值。但是如果已知AI參數是由一個AI功能節點所定義,那麼使用特定解析方法可能是一個更好的選項,因為它不需要許多參數,並且稍微快一點。
一個AI功能節點也可以有一個AIParam
欄位,其允許建立巢狀AI功能。
在視覺編輯器上
當在動作及決策上宣告一個公共AIParam
時,它將顯示在視覺編輯器上,並且您將能夠手動或從特製化節點來定義其值。舉例而言,設想一個public AIParamInt IncreaseAmount
:
除了前述提到的類型之外,也可以針對列舉來建立AI參數。為此,您需要針對您所需的特定列舉來建立您自己的AI參數類型。這裡是針對它的程式碼片段:
C#
// Considering this enum:
public enum BotType { None, HFSM, GOAP };
// Create a new AIParam class based on that enum:
[System.Serializable]
public unsafe sealed class AIParamBotType : AIParam<BotType>
{
public static implicit operator AIParamBotType(BotType value) { return new AIParamBotType() { DefaultValue = value }; }
protected override BotType GetBlackboardValue(BlackboardValue value)
{
int enumValue = *value.IntegerValue;
return (BotType)enumValue;
}
protected override BotType GetConfigValue(AIConfig.KeyValuePair config)
{
return (BotType)config.Value.Integer;
}
}
AI內容
機器人SDK附有一個資料容器的執行方式,其可用於在代理更新常式上傳送其內容特定的資料。
並不是強制使用這類內容的容器,但是可用於輔助從使用者端點取得資料,比如一個HFSM的AIAction.Update()
、一個BT的Leaf.OnUpdate()
等等。
使用它的主要理由是,提供額外的資料到幀或實體參照以外的使用者節點。因此,可以避免大量的重複使用的程式碼,比如frame.Get<MyComponent>(entityRef)
,其有時候對於一個代理的一個單一的更新,需要完成許多次。
有了AI內容,可以在更新常式的最初的時候,放資料到它之中(比如,在調用HFSMManager.Update
之前,這也適用於BT、GOAP及UT的情況)。
所以舉例而言,這裡的內容的目的在於,以有關於一個特定的代理的內容的資料來填入它。可能儲存它的AI黑板元件。可能是一些其他的自訂元件。或者可能是代表代理更新邏輯的某個整數。
總的來說,這裡是一些讓它運行所需的程式碼片段。這裡是一些HFSM的示例,不過同樣的情形也適用於BT、GOAP,及UT:
擴展AI內容架構
- 建立一個新的檔案,依您的需要來選擇名稱及位置。像是一個
AIContext.User.cs
; - 在它之中,以所需的欄位,建立一個AI內容架構的
partial
宣告:
C#
namespace Quantum
{
public unsafe partial struct AIContext
{
public readonly HFSMAgent* HfsmAgent;
public readonly AIBlackboardComponent* Blackboard;
public AIContext(HFSMAgent* hfsmAgent, AIBlackboardComponent* blackboard)
{
this.HfsmAgent = hfsmAgent;
this.Blackboard = blackboard;
}
}
}
- 容器的配置已經準備完成。現在在每次更新代理時建立內容物件,並且以一個
ref parameter
來傳送它:
C#
AIContext aiContext = new AIContext(hfsmAgentComponent, blackboardComponent);
HFSMManager.Update(frame, frame.DeltaTime, hfsmData, entityRef, ref aiContext);
- 這樣做,端點將可以存取內容,比如在一個AI動作的更新之中:
C#
namespace Quantum
{
[System.Serializable]
public unsafe class SampleAction : AIAction
{
public override void Update(Frame frame, EntityRef entity, ref AIContext aiContext)
{
// either cash the data in local variables
var agent = aiContext.HfsmAgent;
var blackboard = aiContext.Blackboard;
// or use it right away where needed
}
}
}
重要考量
您需要謹慎地處理AIContext
。雖然有其他的使用方式,但是在每一幀從頭開始建立它,並且填入它的資料 是最安全地使用它的方式;
同時,內容的主要目的是,提供一個好的方式來讀取關於內容的資料,以協助決策。組建它的目的不是支援在內容中儲存資料,並且在動態的情況下改變它,即便這樣做是可能的。按需要使用它,並請確保謹慎處理,以避免難以追蹤的問題。
停用
針對各個AI模型,有一些特定的節點可以被 停用。這基本上從編譯流程中停用了該部分的邏輯,且不需要刪除/中斷連接任何東西。讓我們分析在不同的AI模型上有哪些東西可以被停用:
HFSM的特定情況
停用狀態
忽略了到已停用狀態的轉換。同時,在該狀態中的任何動作將不會被執行。
附註: 如果您以一個已停用的預設狀態來編譯一個HFSM,您將得到一個錯誤。
為了停用一個狀態節點,以右鍵按一下狀態,然後選擇「停用/取消停用狀態」。
當它被停用時,它將顯示為透明。
停用轉換
您可以透過以右鍵按一下轉換的線,並且選擇「停用/取消停用轉換」,來停用一個轉換。
已停用的轉換將在編譯時被忽略。
可以停用任何類型的轉換:一般轉換、任何轉換、轉換集以及轉換到入口網站。
GOAP的特定情況
已停用的工作不會被新增到GOAP用來制定計劃的可能的工作之中。
為了停用一個工作節點,以右鍵按一下狀態,然後選擇「停用/取消停用工作」。
當它被停用時,它將顯示為透明。
對於HFSM及GOAP
停用動作
可以停用任何動作,不論它在動作清單上的位置。
已停用的動作將在編譯時被忽略,並且只會執行鏈中的下一個動作,前提是已停用的動作含有任何下一個動作的話。
也可以以右鍵按一下任何動作節點,來存取它。
機器人SDK系統
機器人SDK的套件預設附有兩個層級:
BotSDKSystem
:用於自動化一些流程,比如解除配置黑板記憶體、以實體原型含有的資料來初始化HFSM/BT代理等等;BotSDKDebuggerSystem
:用於在Unity側,針對偵錯工具來收集重要資訊;
為了使用它們,只需在您的SystemSetup
層級上新增new BotSDKSystem(),
和/或new BotSDKDebuggerSystem()
。
附註: 並 不是強制 使用這些系統。這裡完成的任何事情都可以在您自己的層級中完成;
附註2: 請注意有些事情可能已經在您自己的程式碼裡面的其他部分完成了,所以請謹慎處理,透過新增這個系統來引進問題。在新增系統之前,看一下它的功能,然後立即使用它,或是將它的一些邏輯放到您自己的程式碼中。
視覺編輯器評論
將評論新增到視覺編輯器中是非常方便的。為了做到這點,選取任何節點(狀態/工作/動作/決策/常數,等等),並且 按「G」 以新增一個評論區域。然後,按一下「評論」標題文字,並且根據您的需要來改變它。也可以新增評論到超過一個節點。為了選取超過一個節點,在Windows作業系統上持續按著「Ctrl」按鈕,或是在Mac作業系統上持續按著「Command」按鈕。
更改編譯輸出資料夾
預設下,機器人SDK的編譯生成的資產,將被放到Assets/Resources/DB/CircuitExport
資料夾。可以透過選取位於Assets/Photon/BotSDK/VisualEditor/CircuitScriptables
資料夾的名為SettingsDatabase
的資產,來改變這個。然後,尋找名為Bot SDK OutputFolder
的欄位,並且按照您的需要來更改它。請確保目標資料夾已經被建立。
同時,總是建立一個名為CircuitExport
的上層資料夾,並且所有子資料夾將被建立在其中。
選擇已儲存的歷史大小
機器人SDK預設儲存5個歷史的輸入項目到視覺編輯器檔案。如果當您關閉/開啟您的AI文件時,您希望在工作階段之間維持歷史,這相當有用。但是AI檔案大小將增加,因為AI迴路變得更大,這取決於您儲存的歷史輸入項目有多少。
所以您可以選擇您希望儲存的歷史輸入項目的數量,其中如果您不需要儲存歷史,您甚至可以設定為零,歷史將只在您重新開啟您的AI檔案時重新載入,其也發生於您關閉/開啟Unity的時候。
為了更改歷史輸入項目的數量,選取位於Assets/Photon/BotSDK/VisualEditor/CircuitScriptables
資料夾的名為SettingsDatabase
的資產。然後,尋找名為Save History Count
的欄位。