Hierarchical Finite State Machine
新しいHFSMの作成
エディターのトップバーで(+)ボタンをクリックし、オプションState Machineを選択します。

これを行うと、HFSMファイルを保存するように求められます。
好きな場所に保存してください。
これにより、ビジュアルエディター で行った作業を永続化するために使用されるデータアセットが作成されます。

注:ここで選択する名前は、ビジュアルエディターで行ったことをコンパイルするときにさらに生成される別のデータアセットの名前になります。
これが実際にボットを駆動するために使用されるデータアセットになるため、この時点で名前を選択できます。

ファイルを保存すると、Bot SDKのメインウィンドウに次のような単一の NewState が表示されます。
それでは、この初期ステートを詳しく見てみましょう。

- HFSMの初期ステートを示します。
- ステートの名前。
- ステートの遷移リスト。
新しいステートの作成
新しいステートを作成するには、エディターウィンドウの空のスペースを右クリックし、Create New State を選択します。


ステートの編集
ステートを編集するには、対象のステートを右クリックし、[Edit This State]を選択します。

ここで、ステートの名前を定義し、遷移を削除し、その位置を並べ替えることができます。
遷移の位置を並べ替えても優先順位は変わりません。
視覚的な調整にのみ使用されます。
ステートの編集が完了したら、Enter を押して変更を適用する必要があります。Esc を押すと、変更を破棄できます。
ステートビューの最小化
時間内にHFSMに多くの遷移が行われることはよくあり、その構造を理解することは困難です。
ステートのノードで、Minimize ボタンをクリックして、外観をより良く整理します。

before と after を分析しましょう。


2つのステート間の遷移の作成
2つのステート間の遷移を作成するには、任意のステートの左/右端にある小さな円をクリックします。
次に、別のステートをクリックすると、新しい遷移が作成されます。
ターゲットステートをすぐに作成する場合、別のステートをクリックする代わりに空の空白スペースをクリックすると、エディターは作成パネルを表示します。

新しい遷移を作成すると、その色は赤になります。これは、遷移がまだ定義されていない ことを示しています。
遷移とインタラクトする方法は4つあります:
- マウスが遷移の上にあるとき、ハイライトされます。
- 遷移を左クリックすると、小さな円が遷移の方向を示します。
- 遷移をダブルクリックすると、そのサブグラフに入ります。
- 右クリックして遷移サブグラフに入ったり、遷移をミュートしたり、削除したりできます。
ここで最初に分析するのは、すでにそこに作成されているノードです。

これは、実際に遷移を定義するために使用されるノードです。
ここには4つの重要な概念があります。
- このノードの名前は、起点ステートとターゲットステートを示します。右クリックメニューを使用して 変更できます。意味のある名前をつける、上の階層に表示されるようにすると条件が何なのかわかりやすくなります。
- 最初のスロットは、遷移を行う イベント を定義するために使用されます。
- 2番目のスロットは、遷移を行う 決定 を定義するために使用されます。
- 3番目のスロットは、同じノードから来る すべての遷移間の実行順序を定義するために使用されます。
この遷移の簡単な決定を定義することから始めましょう。
これを行う際、空白スペースを右クリックすると、作成可能な決定が表示されます。

簡単にするために、TrueDecision を選択しましょう。
もちろん、この決定の結果は常にTrueです。

この決定の結果がどこに送られるかを定義するアウトバウンドスロットがあります。
したがって、Result スロットの横にある円を左クリックして、Decisionスロットに接続します。

以上です。単純な遷移を定義しました。
これにより、ボットが NewState にあり、HFSMが更新されるたびに、ボットは NewState1 に遷移します。
Decisionフィールドの値を編集する には、Decisionをクリックしてから、フィールド自体をクリックします:

ステートの編集が完了したら、Enter を押して変更を適用する必要があります。Esc を押すと、変更を破棄できます。
非常に単純な遷移が定義されたので、トップビューに戻ってどのように見えるかを確認しましょう。
トップバーのパンくずリストにあるRootボタンをクリックすると、ステートビューに戻ることができます。

または、左側のパネルを使用してステートをナビゲートできます。

遷移が赤ではなくなっていることがわかります。
これは有効な遷移であることを示しています。

遷移の優先順位を定義する
あるステートから他のステートへの遷移が複数ある場合、最初に評価される遷移を定義することができます。
この順序を定義するには、Priority スロットを使用します。

ステートノードで遷移の優先順位を確認できます。

遷移が評価される順序は次のとおりです:高い優先度から低い優先度。
これにより、最初に確認する必要がある遷移を定義できます。
注: ステートの遷移に定義された優先順位は、別のステートの遷移に影響を与えません。
新しい遷移の作成
現在、2つのステート間の遷移は1つのみです。複数の遷移を持つステートがないため、優先度フィールドは役に立ちません。
新しい遷移を作成するには、ステートの下部にマウスを置いて (+) ボタンが表示されるようにする必要があります。

次に、最初の遷移と同じように、新しい遷移を定義できます。

特別な遷移タイプ
遷移セット
Transition Set は、多くの遷移をグループ化するのに使用できます。
多くのステートが単一の遷移セットを指すことができるため、以前に定義された遷移セットを再利用することが役立ちます。
新しい遷移セットを作成するには、空のスペースで右クリックし、Create New Transition Set を選択します。
これにより、ステートノードに非常によく似たノードが作成されます。1つの未定義の遷移で始まり、その下部にマウスを移動させると複数の新しい遷移を作成することができます。:

これで、この一連の遷移を再利用する多くのステートを持つことができます。
以下に例を示します。

右上隅のボタンを使用して、遷移セットを最小化することもできます。

すべての遷移
同じ階層レベルの任意のステートから遷移を行う必要がある ことを定義する場合、Any Transitions が非常に便利です。
階層については、このドキュメントでさらに説明します。
右クリックメニューから新しい「任意の遷移」を作成できます。
ここで、「任意の遷移」のターゲットを定義する必要があります。

上記のサンプル画像では、階層のそのレベルにあるすべてのステートは、ANYノードからの遷移を考慮します。
ANY Transitionを無視するステートのリストを定義することができます。 Excluded List メニューからこれらを選択します。

ポータル遷移
このタイプの遷移は、HFSMを強制的にステートから 階層の任意のレベル の他のステートに移行させることを目的としています。
右クリックメニューから新しいポータル遷移を作成できます。

ここで、このポータルがHFSMを使用するステートを定義する必要があります。
ドロップダウンメニューをクリックして選択します。
ターゲットステートの名前は、次の階層に基づいていることに注意してください。

ポータルのターゲットを定義したら、その遷移を考慮するステートを定義するだけです。

構成された決定
単一の決定を使用して遷移を定義する以外に、いくつかの複合決定を作成することもできます。
Bot SDKには、使用できる3つの論理的な決定が既に付属しています。
以下は、AND、OR、およびNOTの決定に基づいた構成決定のサンプルです。




イベント
quantum_code プロジェクトで新しい決定を作成、コンパイル、セットアップなどせずに遷移を評価したい場合は、イベントを利用できます。
これらは非常に簡単な方法で動作します。イベントがトリガーされると、現在のステートの遷移はそのイベントがリッスンされているかどうかを確認します。
いずれかの遷移がそのイベントをリッスンする場合、それが取得されます。
コードの面では、これはイベントをトリガーするために必要なことです。
任意の quantum_code スクリプトで、次を実行します:
C#
HFSMManager.TriggerEvent(f, &guy->Fields.HFSMAgent.Data, (Entity*)guy, "SomeEventName");
これらの呼び出しをアクション/決定に追加することができます。また、システム内など、他のロジックでより自由に呼び出しを行うこともできます。
新しいイベントを作成するには、イベントセッションで、左側のパネルにある**(+)** ボタンをクリックする必要があります。

それを行うと、新しいイベントを作成するように求められます。
編集または削除 したい場合は、イベントをダブルクリックしてメニューを表示できます。
遷移サブグラフにイベントを配置するには、イベントをドラッグアンドドロップします。
次に、決定に対して行ったことと同様に、イベントのアウトバウンドスロットを遷移のイベントインバウンドスロットにリンクできます。

注: 決定とは異なり、複合イベントの定義はなく、遷移は接続された複数のイベントを受け入れません。
1つだけのイベント で定義された決定を伴う遷移は、有効な遷移 と見なされます。
イベントと決定 の両方を設定することで遷移を定義できます。
これがある場合、その遷移は、イベントがトリガーされ、決定条件の結果が真である場合にのみ発生します。

アクションを定義する
ステートマシンのフロー作成方法について(ステートと遷移で)お話ししましたので、ステートのアクションを定義する方法について見ていきましょう。
本件の詳細についてはこちらを参照してください。: Defining Actions
ミュート
自分のAIをテストする場合、ノードをミュートしていくつかのロジックを一時的に無効化することが便利な場合があります。ノードのミュートについての詳細はこちらを参照してください。: Muting
階層
ステートのサブグラフ上に、新しいステートのセットを作成できます。
NewState サブグラフ上にあり、AnotherStateという新しいステートを作成するとします。
これを行うと、これら2つのアクションの間に関係が作成されます。NewStateは親であり、AnotherStateは子です。
それを行うと、親 と 子ステートのアクションと遷移の両方が実行されます。
これにより、ステートマシンを別のステートマシン内にカプセル化できます。
これは、たとえば、3つの動作レベルを持つボス を持っている場合に非常に役立ちます。簡単なモードで開始し、HPの半分に達すると少し難しくなり、HPが10%になるとさらに難しくなります。
上のグラフで3つの主要なステートを作成できます。これらの各ステートには、ボスの難易度を定義する独自のステートマシンがあります。
これにより、HFSMの組織がより整理されます。
し、Create New State オプションを選択します。
これにより、左側のメニューで視覚化できる階層の複雑なレベルを持つことができます。

注: これらのボタンをクリックして、階層をナビゲートできます。
また、これらのステートのいずれかを右クリックして、新しいPortal Transitionを作成することもできます。
重要: HFSMの階層のすべてのレベルに対して、デフォルトステートを定義することもできます。これが、親ステート間を遷移するときに入力される子ステートの定義です。デフォルトステートを定義するには、任意のステートノードを右クリックして、Make Default Stateを選択します。
HFSMのコンパイル
作成したHFSMを実際に使用するには、実行した内容をコンパイルする必要があります。
コンパイルするには、2つのオプションがあります。

- 左ボタンは現在開いているドキュメントのみをコンパイルするために使用されます
- 右ボタンはプロジェクトにあるすべてのAIドキュメントをコンパイルするために使用されます
HFSMファイルは、"Assets/Resources/DB/CircuitExport/HFSM_Assets"にあります。

ボットが使用するAIの設定
最終的に作成されたAIを使用するには、コンパイルされたアセットを参照するだけです。
GUIDに基づいてアセットを読み込むか、目的のAIアセットを指す Asset Link を作成することでそれを行うことができます。

HFSMコーディング
DSLの観点から言えば、HFSMAgent をエンティティの構造体として、または好きな場所で使用できます。
- HFSMAgentは、フィールドまたはエンティティのコンポーネントとして使用できます。
- エンティティを必要とせずに、構造体またはグローバルスペースで宣言できます。
ゲームフローの作成、ゲームの開始時に実行するアクションの定義、ゲームの現在のステートを変更するタイミングの定義など、エンティティの範囲外のアクションをHFSMで実行できるため、重要です。
Agentを初期化する
最初に、EntityPrototypeを使用していない場合 の新しいHFSMAgentを作成方法としエンティティへの追加方法の例を以下にあげます。
C#
var hfsmAgent = new HFSMAgent();
f.Set(myEntity, hfsmAgent);
次に、アプリケーションに適合する任意の時点で、そのエンティティに対してVisual EditorからコンパイルしたHFSMRoot
アセットに基づく HFSMManager.Init メソッドを呼び出す必要があります。
EntityPrototypes使用の有無を問わず、以下の初期化処理を行います。
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の初期化手順に必要な情報がすべてHFSMAgent自体に含まれていれば、エンティティからコンポーネントを取得して必要なデータを取り出せます。以下を参照ください。
var hfsmAgent = f.Get<HFSMAgent>(myEntity);
"OnComponentAdded"コールバックを使用して初期化する
EntityPrototypeの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);
}
// ...
}
Agentをアップデートする
エンティティのHFSMAgentが初期化されたので、以下で必要な時にUpdateメソッドを呼び出すだけです。
C#
HFSMManager.Update(f, f.DeltaTime, myEntity);
これにより、HFSMが初期ステートのデータを実行して他のステートにアクションと遷移を行うようになります。
これで、AIが既にVisual Editor上に作成したフローを実行しているはずです。
Agentを初期化し更新するサンプルシステム
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);
}
}
}
}
ActionsとDecisionsのコーディング
自分のActionを作成するには、こちらのインストラクションに従います。: アクションをコーディング
自分のDecisionの作成は、とても似ています。
ただし、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;
}
}
}
新しいアクションを追加する場合、追加のステップがあります 。それは、いずれかの.qtn
ファイルで、作成したActionに対するアセットのインポートを宣言する必要があるということです。いずれかのqtn ファイルに移動して新しいActionを以下の様にインポートしましょう。
asset import TrueDecision;
フィールド値を定義
Actions/Decisionsフィールドの値設定に関する代替案についての詳細は、こちらを参照してください。フィールド値を定義:
AIParam
AIParamの使用方法についての詳細はこちらを参照してください。これは、様々な方法で定義できる柔軟なフィールドを用意する場合に便利です。手動での設定、またはBlackboard/Constant/Configノードから設定します。: AIParam
AIContext
エージェントコンテキストの情報をパラメータとして渡す方法については、AIContextを参照してください。
BotSDKSystem
Blackboardメモリの割り当て解除などのプロセスの自動化に使用するクラスがあります。詳細はBotSDKSystemを参照してください。
デバッガ
Bot SDKには、独自のデバッグツールが付属しています。開発者は、実行時にHFSMエージェントをクリックして、Visual Editorで強調表示された最新のエージェントのフローを確認できます。こちらは、Bot SDKサンプルプロジェクトで動作するデバッグツールのサンプルです:

上記のgifに示されているように、エージェントの現在のステートと、そのステートに至った最新の3つの遷移を確認できます。
ブルーの遷移が最新です。黒くなっている前回の遷移よりも多くの円が線上を通っています。
さらに、階層ビューで現在のステートを調べることもできます。矢印が表示されているステートは、HFSMが現在そのステートにあることを表しています。現在のエージェントの階層の深さを確認する際にグラフを確認する必要がないので便利です。

デバッガの使用
自分のプロジェクトでデバッガを使用するための手順です:
- 自分の
SystemSetup.cs
ファイルでBotSDKDebuggerSystem
を有効にする。この特定のシステムの使用は任意で、どこかにデバッギングロジックを持っておきたい場合は自分のカスタムシステム内の認証済みフレームでBotSDKDebuggerSystem.OnVerifiedFrame?.Invoke(f);
を呼び出しを行う。; - ビジュアルエディタで一番上のパネルにある虫のアイコンをクリックする。アイコンが緑色の時は、デバッギングが有効。

どのエンティティをデバッグするか選択するには2通りの方法があります。選択済みのGame Objectと関連付けることもできるし、インスペクターウィンドウで選択することもできます。このうち1つを選んでもいいし、両方組み合わせることもできます。:
Game Objectからデバッギングする:
HFSMAgent
をコンポーネントとしてもつQuantumエンティティを表すプレハブ・エンティティプロトタイプを選択する;BotSDKDebugger
を1に追加する;ランタイムの間Bot SDKウィンドウを開いたまま、
BotSDKDebugger
が追加されたゲームオブジェクトを選択する。;
デバッグ用インスペクターウィンドウからデバッグする:
- シミュレーション側では、Agentエンティティをデバッガ・ウィンドウに登録する必要があります。以下のように呼び出して行うことができます。
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内でアプリケーションのプロファイリングを行う場合、プロファイリングの一部がデバッガに関連している可能性がありますので、プロファイリング中はデバッガを無効にすることを検討してください。
PS: デバッガウィンドウには、エンティティビューを持たない エンティティも表示されるため、それらのHFSMをデバッグするにはこの方法を用います。;
PS2: 現在、エンティティにリンクされていない、DSLグローバルにあるようなエージェントのデバッグはできません。以降のバージョンで追加される予定です。
Visual Editorのコメント
Visual Editorでのコメントの作成方法に関する詳細はこちらを参照してください。:Visual Editorのコメント
コンパイル出力フォルダを変更する
デフォルトで、Bot SDKのコンパイルで生成されたアセットはAssets/Resources/DB/CircuitExport
フォルダ内に置かれます。エクスポートフォルダの変更方法についてはこちらを参照してください。:コンパイル出力フォルダを変更する
保存される履歴サイズの選択
Bot SDKファイルに保存した履歴エントリの大きさを変更することができます。本件の詳細はこちらを参照してください。: 保存される履歴サイズの選択
フレーム中に行われること
Bot SDKの主なエントリポイントは次のとおりです。
HFSMManager.Update
はエージェントを更新するために常に呼び出されます。HFSMManager.Init
はエージェントの初期化に使用されます。HFSMManager.TriggerEvent
はトリガーされたイベントを考慮して、移行チェックを強制します。
これらのメソッドが呼び出されたときにフレーム中に何が起こるかを視覚化するために、フローグラフを作りました。
