This document is about: QUANTUM 3
SWITCH TO

Shared Concepts

在此找到一些適用於多種 Bot SDK AI 模型的共通概念。

定義動作

在 HFSM 編輯器中,每個狀態內部都有一個子圖。在 UT 編輯器中,每個考量(Consideration)也有一個子圖。這些子圖可以透過雙擊相應節點來進入。
在這些子圖中,可以 創建動作節點

進入子圖時,頂部欄會顯示一個麵包屑導航,標示當前所在的層級深度。
你可以使用這些按鈕來導航到層級結構的上一層。

Breadcrumb

子圖中已經定義了一個重要節點:動作根節點(Actions Root node)。

Subgraph Actions

在 Bot SDK 編輯器中
有三個動作列表可以執行:

  1. On Enter 列表定義了當 HFSM 進入此狀態時要執行的動作;
  2. On Update 列表定義了每次更新 HFSM 時要執行的動作(通常是每一幀);
  3. On Exit 列表定義了當 HFSM 離開此狀態時要執行的動作。

在 UT 編輯器中
有三個動作列表可以執行:

  1. On Enter 列表定義了當 UT 開始執行考量時要執行的動作;
  2. On Update 列表定義了每次更新 UT 時要執行的動作(通常是每一幀);
  3. On Exit 列表定義了當 UT 停止執行某個考量(因為選擇了另一個考量)時要執行的動作。

要定義這些動作列表,點擊動作根節點右側的箭頭,然後點擊任何動作的輸入插槽,或點擊空白處直接創建新動作:

Actions Sample 1

這裡重要的是,你可以定義任意多個連結的動作,它們會在同一幀中按順序執行。
只需使用箭頭按鈕將它們連結起來:

Actions Sample 2

你可以隨意重新排序動作。
你也可以保留未連結的動作集以供後續使用,這樣它們不會被刪除,也不會被執行:

Actions Sample 3

注意,你可以透過點擊輸入插槽來定義動作欄位的值。

按 Enter 套用變更,或 按 Esc 放棄變更。

Action Fields

如前所述,編輯器已經預定義了一些動作和決策。
這些是為了讓開發者能夠快速開始。

在你的專案中,你需要實現特定的動作和決策。
讓我們看看如何實現。

編碼動作

要創建新動作,你需要打開 quantum_code 解決方案。

quantum.code 專案中,你可以創建任何繼承自AIAction抽象類別的類別。
這樣做時,你需要實現Update方法,該方法會在更新代理(HFSM 或 UT)時被調用。

對於 HFSM,這就是在你定義的動作列表(OnEnterOnUpdateOnExit)中執行的內容。例如,當進入某個 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
        }
    }
}

定義欄位值

在 Bot SDK 中,可以在程式碼中宣告公開欄位,以便這些欄位出現在視覺編輯器中。例如,在 HFSM 中,可以對動作和決策程式碼進行此操作。在編輯器中,使用者可以定義這些欄位的值。

設定這些值的簡單方法是直接點擊欄位並賦值。但還有其他選項:

  • 使用黑板節點(Blackboard nodes);
  • 使用常數節點(Constant nodes);
  • 使用配置節點(Config nodes);
  • 使用 AI 功能節點(AIFunction nodes)。

由於黑板節點已在黑板文件中說明,讓我們深入探討常數/配置/AI功能節點。

常數面板

可以從左側面板定義和使用常數。

定義常數後,可以將常數節點拖放到動作和決策(在 HFSM 中)的輸入插槽中。單個常數節點可以多次用作輸入,從而更輕鬆地為多個欄位定義相同的值。

常數的另一個重要特性是,當在設置選單中更改其值時,所有源自該常數的節點都會相應更新,這使得在 HFSM 的多個部分定義值並在後續更改這些值變得更加容易。

使用左側選單定義新常數,點擊常數分頁上的 (+) 符號。

HFSM Asset

然後選擇其NameTypeDefault Value並儲存。定義常數後,可以將其拖放到圖形視圖中並連結到輸入插槽。

HFSM Asset

配置面板

可以為使用相同 HFSM/BT/UT 的多個代理設定不同的常數值。這在例如你想要為不同難度的機器人使用相同行為邏輯時非常有用。簡單模式下的射擊機器人可以將反應時間設定為「2 秒」,而困難模式下可以設定為「0.5 秒」。這可以透過配置面板輕鬆實現。

使用左側面板創建配置值,這些值會編譯成類型為AIConfigAsset的新資料資產。編譯後,你可以在AIConfig_Assets資料夾中找到名為<DocumentName>DefaultConfig的資產。

這些資產可以在模擬中用於檢索常數值。

讓我們創建一個非常簡單的配置佈局:

CreateConfig

除了從頭創建新配置欄位外,還可以將常數轉換為配置,反之亦然。

ConvertToConfigurable

編譯文件後,生成的資產如下:

Defaultconfig

最後,為了從該資產創建變體以便定義不同的常數值,在 Unity 的專案選項卡中使用右鍵選單,選擇Create/Quantum/Assets/AIConfig

這將創建一個非常簡單的配置資產,它會基於另一個配置資產。填寫預設配置欄位並點擊「更新配置」以鏡像預設配置資產。然後你可以根據需要更改值。此外,點擊「重置為預設」可以將值恢復為原始配置資產的值。

ConfigVariation

現在,要使用這些配置,有幾種方法:

  1. 直接從配置資產讀取,提供配置鍵並根據配置類型檢索值:

    var myBoolean = myConfig.Get("Key").Value.Boolean;

    類型可以是:Integer、Boolean、Byte、FP、FPVector2、FPVector3 和 String。

  2. 或與AIParam類型一起使用。

    舉例說明:

    首先,在任何動作/決策中創建一個 AIParamFP 欄位。編譯它,使其出現在視覺編輯器中;

    然後,從左側面板拖放一些配置值並將其連結到 AIParam 欄位;

ConfigNode

​ 然後編譯文件。在程式碼中,使用 AIParam API 檢索值。這將根據傳遞為參數的配置資產擷取相應的配置值:

// Considering that the variable "AttackRange" is of type AIParamFP
FP rangeValue = AttackRange.Resolve(f, blackboard, myConfig);

基本上,這裡的技巧是根據視覺編輯器中的內容創建預設配置資產,使用配置面板,然後創建其變體,並根據需要使用 AssetRefs 連結它們。

HFSMAgentBTAgentUTAgent已經包含一個欄位來引用特定代理/實體的配置資產,僅為方便起見。因此,你可以使用它來創建自己的引用:

// 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;
btAgent->Config = config;
utAgent->Config = config;

// Then, when you need to GET the Config:
hfsmAgent.GetConfig(frame);
btAgent.GetConfig(frame);
utAgent.GetConfig(frame);

AIParam

有多種方法可以定義欄位值的來源。目前可以透過使用黑板、常數和配置來實現。因此,在程式碼中讀取這些值也有不同的方法。因此,如果開發者需要更改來源類型(例如從黑板節點更改為常數節點),則還需要更改源程式碼。

但為什麼更改來源類型會有用?

  • 為了簡單起見,可以直接手動定義值;
  • 如果值可以在運行時更改,可以將其存儲在黑板上,因此使用黑板節點定義它;
  • 如果值不變,但你希望它來自節點以使圖形更靈活,則使用常數節點定義它;
  • 如果上述情況適用,但你需要它因代理而異,則使用配置節點。

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
  • 在內部,它已經檢查了 AIParam 在視覺編輯器中是如何定義的?是手動定義的,還是來自任何專門的節點。

AI功能節點

透過 AI 功能節點,可以預定義多種類型的「Getter」節點。其主要目的是創建特定節點,這些節點將根據你的遊戲特定需求返回值。

AI功能節點的基礎類型是:

  • AIFunctionByte;
  • AIFunctionBool;
  • AIFunctionInt;
  • AIFunctionFP;
  • AIFunctionFPVector2;
  • AIFunctionFPVector3;
  • AIFunctionEntityRef

要創建自己的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
    }
  }
}

如上述示例所示,你可以使用通用的 Resolve 方法,該方法根據視覺編輯器中定義的來源(黑板、配置、AI功能節點)返回值。但如果已知 AIParam 是由 AI 功能節點定義的,則使用特定的 Resolve 方法可能是更好的選擇,因為它不需要許多參數且速度稍快。

AI功能節點也可以有一個 AIParam AIParam欄位,從而允許創建嵌套的AI功能。

在視覺編輯器中

當在動作和決策中宣告公開的AIParam時,它會出現在視覺編輯器中,你可以手動或從專門的節點定義其值。例如,考慮一個public AIParamInt IncreaseAmount

AIParam Sample
### 為自定義列舉創建 AIParam

除了之前提到的類型外,為列舉創建 AIParam 也可能很有用。為此,你需要為所需的特定列舉創建自己的 AIParam 類型。以下是程式碼片段:

C#

// Considering this enum:
public enum BotType { None, HFSM, BT, UT };

// 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 上下文

Bot SDK 帶有一個資料容器的實現,可以幫助在代理的更新例程中傳遞其上下文特定的資料。

使用此上下文容器並非強制性的,但可以用於方便從使用者端點獲取資料,例如 HFSM 的 AIAction.Update()、BT 的Leaf.OnUpdate()等。

使用它的主要原因是提供除幀(Frame)和實體引用(EntityRef)之外的額外資料給使用者程式碼。這樣可以避免許多樣板程式碼,例如frame.Get<MyComponent>(entityRef),這些程式碼有時需要在單個代理的更新中多次執行。

透過 AI 上下文,可以在更新例程的最開始(例如在調用HFSMManager.Update之前,BT 和 UT 也是如此)將資料放入其中。


舉例來說,這裡的上下文目的是填充與特定代理上下文相關的資料。可能是儲存其 AIBlackboard 組件,或其他自定義組件,或某些對代理更新邏輯有意義的整數。


以下是一些需要運行的程式碼片段。這些是 HFSM 的示例,但同樣適用於 BT 和 UT:

擴展 AIContextUser 結構

  • 創建一個新文件,名稱和位置隨你喜好,例如AIContextUser.cs
  • 宣告AIContextUser結構的partial定義,並帶有所需的特定欄位。以下程式碼僅為示例:

C#

namespace Quantum
{
  public unsafe partial struct AIContextUser
  {
    public readonly AIBlackboardComponent* Blackboard;
    public readonly HFSMAgent* HFSMAgent;

    public AIContextUser(AIBlackboardComponent* blackboard, HFSMAgent* hfsmAgent)
    {
      Blackboard = blackboard;
      HFSMAgent = hfsmAgent;
    }
  }
}
  • 更新 AI 代理時,創建一個新的AIContext實例並填充特定的UserData,然後將其傳遞給 Update 方法:

C#

AIContext aiContext = new AIContext();
AIContextUser userData = new AIContextUser(blackboard, hfsmAgent);
aiContext.UserData = &userData;

HFSMManager.Update(frame, frame.DeltaTime, hfsmData, entityRef, ref aiContext);
  • 從使用者端點,使用擴展方法存取使用者特定的上下文以進行簡單轉換:

C#

namespace Quantum
{
    [System.Serializable]
    public unsafe class SampleAction : AIAction
    {
        public override void Update(Frame frame, EntityRef entity, ref AIContext aiContext)
        {
            var userContext = aiContext.UserData();
            // either cash the data in local variables
            var agent = userContext.HfsmAgent;
            var blackboard = userContext.Blackboard;

            // or use it right away where needed
        }
    }
}

重要注意事項

在管理AIContext時要非常小心。每幀從頭創建並填充資料 是最安全的使用方式,儘管還有其他可能性;

此外,上下文的主要目的是提供一種讀取與上下文相關資料的好方法,以幫助決策制定。它並不是為了支持在運行時存儲資料並更改它而設計的,儘管這是可能的。根據需要使用它,只需確保小心操作以避免難以追踪的問題。

靜音

對於每個 AI 模型,有一些特定的節點可以 靜音。這本質上會在編譯過程中禁用該部分邏輯,而無需刪除/取消連結任何內容。讓我們分析不同 AI 模型中可以靜音的內容:

HFSM 專用

靜音狀態

到靜音狀態的轉換會被忽略。此外,該狀態內的任何動作都不會執行。

注意: 如果你以被靜音的預設狀態編譯一個 HFSM,將會出現錯誤。

要靜音狀態節點,右鍵點擊狀態並選擇「靜音/取消靜音狀態」。
靜音時,它將呈現半透明狀態。

Mute State

靜音轉換

你可以透過右鍵點擊轉換的線並選擇「靜音/取消靜音轉換」來靜音轉換。

靜音的轉換在編譯過程中被忽略。
可以靜音任何類型的轉換:普通轉換、任意轉換、轉換集和到入口的轉換。

Mute Transition

HFSM 和 UT 共用

靜音動作

可以靜音任何動作,無論其在動作列表中的位置如何。
靜音的動作在編譯過程中被忽略,只有靜音動作鏈中的下一個動作(如果存在)會被執行。

也可以透過右鍵點擊任何動作節點來實現。

Mute Action

Bot SDK 系統

Bot SDK 的套件預設包含兩個類別:

  • BotSDKSystem:用於自動化一些流程,例如釋放黑板記憶體、初始化 HFSM/BT 代理與實體原型中包含的資料等;
  • BotSDKDebuggerSystem:用於收集 Unity 端調試器所需的重要資訊;

要使用它們,只需在SystemSetup類別中新增new BotSDKSystem(),和/或new BotSDKDebuggerSystem()

注意: 使用這些系統 不是強制性的。所有功能都可以在自己的類別中實現;

注意 2: 請注意,這些系統中的某些功能可能已經在你的程式碼中實現,因此新增此系統時要注意避免引入問題。在新增系統之前,請先查看它的功能,然後直接使用它,或將其部分邏輯整合到自己的程式碼中。

視覺編輯器註釋

在視覺編輯器中新增註釋可以很方便。為此,選擇任何節點(狀態/任務/動作/決策/常數等...)並 按「G」 以新增註釋區域。然後,點擊「註釋」標題文本並根據需要更改它。也可以為多個節點新增註釋。要選擇多個節點,在 Windows 系統上按住「Ctrl」按鈕,或在 Mac 系統上按住「Command」按鈕。

Commented State
Commented Actions

更改編譯輸出資料夾

預設情況下,Bot SDK 編譯生成的資產會放在Assets/Resources/DB/CircuitExport資料夾中。可以透過選擇位於Assets/Photon/BotSDK/VisualEditor/CircuitScriptables資料夾中的SettingsDatabase資產來更改此設置。然後,找到名為Bot SDK OutputFolder的欄位並根據需要更改它。只需確保目標資料夾已經存在。

此外,始終會創建一個名為CircuitExport的父資料夾,所有子資料夾都創建在其中。

Change output folder

選擇儲存的歷史記錄大小

Bot SDK 預設將 5 條歷史記錄儲存到視覺編輯器檔案中。這在你想在關閉/打開 AI 文件時保持歷史記錄之間很有用。但 AI 文件大小會隨著 AI 迴路的增大而增加,具體取決於你儲存的歷史記錄條目數量。

因此,你可以選擇要儲存的歷史記錄條目數量,甚至可以設置為零(如果不需要儲存歷史記錄),因為歷史記錄僅在重新打開 AI 文件時重新加載(這也發生在你關閉/打開 Unity 時)。

要更改歷史記錄條目數量,選擇位於Assets/Photon/BotSDK/VisualEditor/CircuitScriptables資料夾中的SettingsDatabase資產。然後,找到名為Save History Count的欄位。

History Count
Back to top