This document is about: QUANTUM 3
SWITCH TO

共通コンセプト

ここでは、複数のBot SDK AIモデルで共通するコンセプトを示します。

アクションの定義

HFSMエディターでは、各状態の内にサブグラフが存在します。UTエディターでも、各コンシダレーションはサブグラフを持ちます。サブグラフは、対応するノードをダブルクリックすることで開き、そこでアクションノードを作成できます。

サブグラフを開くと、上部バーに現在の階層深度を示すパンくずリストが表示されます。
これらのボタンを使用して、前の階層に戻ることができます。

Breadcrumb

サブグラフ上には、重要なノードである「アクションルートノード」が既に定義されています。

Subgraph Actions

HFSMエディター内
実行されるアクションのリストは3つあります。

  1. On Enterリストは、HFSMがこの状態を開始した際に実行されるアクションを定義します。
  2. On Updateリストは、HFSMが更新されるたび(通常は毎フレーム)実行されるアクションを定義します。
  3. On Exitリストは、HFSMがこの状態を終了した際に実行されるアクションを定義します。

UTエディター内
実行されるアクションのリストは3つあります。

  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

エディター上にはあらかじめ定義されたアクションとディシジョンが用意されているので、ここから作業を始めることができます。

実際のプロジェクトでは、ゲーム固有のアクションとディシジョンを実装する必要があります。
ここからはその方法を見ていきましょう。

アクションのコード実装

ゲーム固有のロジックを実行するAIアクションを作成するには、抽象クラスAIActionを継承した新しいクラスを作成します。
そして、様々なAIドキュメントの使用方法に応じてトリガーされるExecuteメソッドを実装します。

重要:新しいAIActionクラスには[Serializable]を付けてください。

C#

namespace Quantum
{
    [Serializable]
    public partial class IdleAction : AIAction
    {
        public override void Execute(Frame frame, EntityRef entity, ref AIContext aiContext)
        {
            // ここにアクションのコード実装を追加する
        }
    }
}

フィールド値の定義

Bot SDKでは、コード内で宣言したpublicなフィールドを、ビジュアルエディター上に表示することができます。HFSMでは、アクションやディシジョンのコードでフィールドを宣言し、エディター上からそのフィールドの値を定義できます。

フィールドをクリックすると、簡単に値を設定することができます。これには様々な選択肢があります。

  • Blackboardノードの使用
  • Constantノードの使用
  • Configノードの使用
  • AIFunctionノードの使用

Blackboardノードについてはブラックボードのドキュメントで説明されていますので、ここではConstant/Config/AIFunctionノードについて詳しく見ていきましょう。

Constantsパネル

左サイドパネルから、「定数(Constants)」を定義して使用することが可能です。

定数を定義した後は、(HFSM上の)アクションやディシジョンの入力スロットに、定数ノードをリンクすることが可能です。一つの定数ノードを複数の入力として使用できるため、同じ値を持つ複数のフィールドを簡単に定義できます。

定数のもう一つの重要な点は、設定メニューから値を変更すると、その定数を利用しているすべてのノードが自動的に更新されることです。これによって、HFSMの様々な所で値を定義して、後から変更することが容易になります。

左サイドメニューの「Constants」タブにある「+」をクリックすると、新しい定数が定義できます。

HFSM Asset

そこからNameTypeDefault Valueを選択して保存します。これで定数が定義されたため、グラフビューにドラッグ&ドロップして、入力スロットにリンクできるようになります。

HFSM Asset

Configパネル

同じHFSM/BT/UTを使用する複数のエージェントに、異なる定数を設定することが可能です。例えば、難易度ごとに異なるBotを作成しつつ、振る舞いは同じロジックを持たせたい場合に便利です。例えばここで、射撃Botの反応速度を、Easyモードでは「2秒」に、Hardモードでは「0.5秒」に設定できます。これはConfigパネルから簡単に設定できます。

左サイドパネルから「コンフィグ(Config)」を作成すると、新しいAIConfigAsset型のデータアセットにコンパイルされます。コンパイル後、<DocumentName>DefaultConfigという名前のアセットがAIConfig_Assetsフォルダーに生成されます。

これらのアセットは、シミュレーション内で定数値を取得するために使用できます。

非常にシンプルなコンフィグを作成してみましょう。

CreateConfig

コンフィグの新しいフィールドをゼロから作成する以外にも、定数をコンフィグに変換したり、その逆も可能です。

ConvertToConfigurable

ドキュメントをコンパイルすると、以下のアセットが生成されます。

Defaultconfig

このアセットを元にして、異なる定数値を定義できるようにするには、UnityのProjectタブで右クリックメニューを開きCreate/Quantum/Assets/AIConfigを選択します。

これによって、別のコンフィグアセットをベースとして参照する非常にシンプルなコンフィグアセットが作成されます。Default Configフィールドを設定し、Update Configをクリックすると、デフォルトのコンフィグアセットが反映されます。その後、値は自由に変更可能です。また、値を元のコンフィグアセットの値に戻したい場合はReset to Defaultをクリックしてください。

ConfigVariation

これらのコンフィグを使用する方法はいくつかあります。

  1. コンフィグアセットでキーを指定して、設定された型の値を直接読み込む

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

    設定できる型は次の通りです:IntegerBooleanByteFPFPVector2FPVector3String

  2. AIParam型と組み合わせて使用する

    具体例:

    最初に任意のアクション/ディシジョンにAIParamFPフィールドを作成します。これをコンパイルすると、ビジュアルエディターにフィールドが表示されます。

    次に、左サイドパネルからコンフィグ値をドラッグ&ドロップして、AIParamフィールドにリンクします。

ConfigNode

そして、ドキュメントをコンパイルします。コードではAIParamAPIから値を取得します。これによって、パラメーターとして渡されたコンフィグアセットに従って設定値が取得できます。

C#

// "AttackRange"変数がAIParamFP型であることが前提
FP rangeValue = AttackRange.Resolve(f, blackboard, myConfig);

基本的には、まずビジュアルエディターのConfigパネルからデフォルトのコンフィグアセットを作成し、それを元に派生したコンフィグアセットを作成し、必要に応じてAssetRefを使用してそれらをリンクします。

HFSMAgent/BTAgent/UTAgentには、利便性のため、特定のエージェント/エンティティ用のコンフィグアセットを参照するフィールドが用意されています。これを使用して、独自の参照を作成することができます。

C#

// コンフィグは任意のソース(カスタムアセット・RuntimeConfig・RuntimePlayerなど)から取得し、コンポーネントに設定する
hfsmAgent->Config = config;
btAgent->Config = config;
utAgent->Config = config;

// そして、必要なタイミングでコンフィグを取得する
hfsmAgent.GetConfig(frame);
btAgent.GetConfig(frame);
utAgent.GetConfig(frame);

AIParam

フィールド値のソースを定義する方法は複数存在します。現在は、ブラックボード・定数・コンフィグが使用可能で、これらの値をコードから読み取る方法も複数存在します。そのため、ソース型を変更する必要がある(例:ブラックボードノードから定数ノードへ変更する)場合は、ソースコードの変更も必要になります。

ソース型が変更できると何が便利なのでしょうか?

  • 簡略化のために、単に値を手動で直接定義することもできます。
  • 値が実行時に変更される可能性がある場合は、ブラックボードノードを使用して値を定義することで、ブラックボードに値を保存できます。
  • 値は変更されないもののグラフの柔軟性を高めるためにノードを使用したい場合は、定数ノードを使用して値を定義します。
  • 上記に該当するもののエージェントごとに異なる値が必要な場合は、コンフィグノードを使用します。

AIParam型は、ソースが突然変更される可能性があるような状況に対応するために用意されました。その使用方法を学ぶ前に、値を読み取るコードの違いを簡単に分析してみましょう。

あるフィールド値が、ビジュアルエディター上で手動で定義された場合、または定数ノードを使用して定義された場合は、値を読み取るコードは単純明快です。

C#

// この場合、値はフィールド自体に直接格納されるため、追加コードは一切不要になる
public Int32 MyInteger;

ブラックボードノードの値を取得するなら、次のようになります。

C#

// ブラックボードアセットから値を読み取る
var value = blackboardComponent->Board.GetValue("someKey");

コンフィグノードなら、次のようになります。

C#

// コンフィグアセットから値を読み取る
var myBoolean = myConfig.Get("Key").Value.Boolean;

ビジュアルエディター上でソースが変更されても、コードを変更する必要がないようにするには、フィールドにはAIParam型を使用してください。主な特徴は次の通りです。

  • Resolveメソッドを持ち、ブラックボードやコンフィグアセットを渡せます。フィールド値のソースを認識することで、メソッドは常に正しい値を返します。(フィールドが手動で定義された場合は)値を直接返し、ブラックボード/コンフィグならその値を返します。そのため、値のソース型は必要なだけ変更可能で、値を読み取るコードは次のようになります。

C#

public AIParamInt MyAIParam;
var value = MyAIParam.Resolve(frame, blackboard, aiConfig);
  • 現在は8つの型に対応しています。

C#

AIParamInt, AIParamBool, AIParamByte, AIParamFP, AIParamFPVector2, AIParamFPVector3, AIParamString, AIParamEntityRef
  • 内部的には、AIParamはビジュアルエディター上でどのように定義されたか?(手動で定義されたのか、特定のノードから定義されたのか)をチェックします。

AIFunction Node

AIFunctionノードによって、様々な型の「Getter」ノードを事前定義できます。主な目的は、ゲーム固有要件に合わせて値を返すノードを作成できるようにすることです。

AIFunction基底クラスはジェネリクス型で、主に次の型をサポートします:byte, bool, int, FP, FPVector2, FPVector3, EntityRef, AssetRef

独自のAIFunctionノードを作成するには、適切な抽象型を継承した新しいクラスを作成して、Execute()メソッド(FrameFrameThreadSafeバージョンがあります)を実装します。以下は、独自コンポーネントで参照されるエンティティの位置を返すAIFunctionノードのサンプルです。

C#

namespace Quantum
{
  [System.Serializable]
  public unsafe class GetEntityPosition : AIFunction<FPVector3>
  {
    public override FPVector3 Execute(Frame frame, EntityRef entity, ref AIContext aiContext)
    {
      MyComponent myComponent = frame.Unsafe.GetPointer<MyComponent>(entity);
      Transform3D* targetTransform = frame.Unsafe.GetPointer<Transform3D>(myComponent->TargetEntity);
      return targetTransform->Position;
    }
  }
}

コンパイル後、コンテキストメニューからAIFunctionが利用可能になります。AIFunctionクラスにフィールドを宣言することも可能で、ビジュアルエディターで値を直接設定できます。

AIFunctionノードのリンクは、AIParam型を使用する必要があります。上記のAIFunctionクラスに基づいてエンティティの位置を取得するHFSMアクションがある場合は、AIParamFPフィールドが必要になります。

C#

namespace Quantum
{
  [System.Serializable]
  public unsafe partial class SampleAction : AIAction
  {
    public AIParamFP TargetPosition;

    public override void Update(Frame frame, EntityRef e)
    {
      // AIParamのソースがBlackboard/Config/AIFunctionノードのどれかわからない場合は、汎用的なResolveメソッドを使用する
      var position = TargetPosition.Resolve(/*args*/);
      
      // ソースがAIFunctionノードで確定している場合は、具体的なResolveメソッドを使用できる
      var position = TargetPosition.ResolveFunction(frame, entity);
      
      // ここから位置に関連するなんらかの処理を行う
    }
  }
}

上記の例で示されるように、ビジュアルエディターで定義されたソース(Blackboard/Config/AIFunction)に応じた値を返したい場合は、汎用的なResolveメソッドが使用できます。ただし、AIParamAIFunctionノードで定義されていることが分かっている場合は、パラメーターが少なく若干高速に動作する具体的なResolveメソッドを使用する方が良い選択肢になります。

AIFunctionノードはAIParamフィールドを持つことも可能で、これによってAIFunctionの入れ子が作成できます。

ビジュアルエディター上での設定

アクションやディシジョンにpublicなAIParamが宣言されると、ビジュアルエディター上に表示され、その値を手動または特定ノードから定義できるようになります。以下はpublic AIParamInt IncreaseAmountの例です。

AIParam Sample

Enum型のAIParam作成

前述の型に加えて、列挙型(Enum)のAIParamが作成できると便利です。そのためには、特定の列挙型に対する独自のAIParam型を作成する必要があります。以下にコードスニペットを示します。

C#

// 次のenumについて考える
public enum BotType { None, HFSM, BT, UT };

// 上記のenumに基づいた新しいAIparamクラスを作成する
[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;
    }
  }

AIContext

Bot SDKにはデータコンテナの実装が用意されており、エージェントの更新ルーチンにおいて、コンテキスト固有のデータを渡すのに役立ちます。

コンテキストのデータコンテナの使用は必須ではありませんが、HFSMのAIAction.Update()やBTのLeaf.OnUpdate()などのエンドポイントからデータを取得することが容易になります。

主な使用目的は、FrameEntityRef以外から追加データを与えられるようにすることです。これによって、エージェントの更新中に複数回呼び出しが必要になることがあるようなボイラープレートコード(例:frame.Get<MyComponent>(entityRef))を大幅に削減できます。

AIContextを使用すると、更新ルーチンの最初の方(例: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();
            // ローカル変数にデータをキャッシュする
            var agent = userContext.HfsmAgent;
            var blackboard = userContext.Blackboard;
            
            // または必要になった際に直接使用する
        }
    }
}

重要な留意事項

AIContextの管理には細心の注意を払ってください。様々な使用方法が存在しますが、毎フレーム新規生成してデータを格納するのが最も安全な使用方法です。

コンテキストの主な目的は、意思決定を支援するコンテキスト関連のデータを読み取る便利な手段を提供することです。コンテキスト内に動的に変更するデータを格納することは可能ですが、それを意図して設計されていません。必要なら使用しても構いませんが、追跡困難な問題を引き起こさないように慎重に取り扱うことを確認してください。

ミュート

各AIモデルには、ミュート可能なノードがいくつか存在します。これは、何も削除/リンク解除することなく、コンパイルプロセスからロジックの一部を無効にします。各AIモデルでミュート可能なノードを見ていきましょう。

HFSM専用

状態のミュート

ミュートされた状態への遷移は無視されます。また、その状態のアクションは実行されません。

補足:ミュートされたデフォルト状態を含むHFSMをコンパイルするとエラーが発生します。

状態ノードをミュートするには、状態を右クリックして「Mute/Unmute Sate」を選択します。
ミュート中は半透明で表示されます。

Mute State

遷移のミュート

遷移をミュートするには、遷移の線を右クリックして「Mute/Unmute Transition」を選択します。

ミュートされた遷移はコンパイル時に無視されます。
あらゆる種類の遷移(通常の遷移・ANY遷移・遷移セット・ポータル遷移)をミュートすることができます。

Mute Transition

HFSMとUTで有効

アクションのミュート

アクションリストの位置にかからわず、任意のアクションをミュート可能です。
ミュートされたアクションはコンパイル時に無視され、(その次のアクションがある場合のみ)次のアクションが実行されます。

アクションノードを右クリックして、ミュート設定を変更可能です。

Mute Action

Bot SDK System

Bot SDKパッケージには、デフォルトで2つのクラスが含まれています。

  • BotSDKSystem:いくつかのプロセス(ブラックボードのメモリ解放・エンティティプロトタイプに含まれるHFSM/BTエージェントの初期化など)を自動化するために使用されます。
  • BotSDKDebuggerSystem:Unity側のデバッガー向けに、重要な情報を収集するために使用されます。

これらを使用するには、SystemSetupBotSDKSystem/BotSDKDebuggerSystemを追加してください。

補足:これらのシステムの使用は必須ではありません。実行される処理はすべて独自クラスで実装可能です。

補足:これらのシステムで実行される処理の一部は、既に独自のコードで実装してしまっている可能性もあるため、システムを追加することで問題が起こらないように注意してください。システムを追加する前に既存実装を確認して、そのまま使用するか、またはシステムロジックの一部を独自のコードに組み込むかを判断してください。

ビジュアルエディターのコメント

ビジュアルエディターにコメントを追加すると便利です。任意のノード(状態/タスク/アクション/ディシジョン/定数など)を選択して「G」キーを押すと、コメントエリアが追加されるので、「コメント」ヘッダーテキストをクリックして内容を自由に変更してください。複数のノードにコメントを追加することも可能で、複数のノードを選択するには、Windowsなら「Ctrl」キー、Mac OSなら「Command」キーを押しながらノードをクリックします。

Commented State
Commented Actions

コンパイル出力フォルダーの変更

デフォルトでは、Bot SDKのコンパイルプロセスで生成されたアセットはAssets/Resources/DB/CircuitExportフォルダーに置かれます。これを変更するには、Assets/Photon/BotSDK/VisualEditor/CircuitScriptablesフォルダーのSettingsDatabesという名前のアセットを選択して、そこにあるBot SDK OutputFolderフィールドを好きな場所に指定します。その際、対象フォルダーが既に作成されていることを確認してください。

なお、親フォルダーCircuitExportは常に作成され、その中にすべてのサブフォルダーが作成されます。

Change output folder

保存される履歴サイズの選択

Bot SDKは、デフォルトでビジュアルエディターに5件の履歴を保存します。AIドキュメントを開閉する際にその履歴を保持したい場合には便利ですが、AIファイルはノード数や履歴件数によってサイズが増加してしまいます。

保存する履歴件数は自由に選択することが可能で、履歴を保存する必要がなければ0件に設定することもできます。履歴は、AIファイルを再度開いた時(Unityを開く/閉じるを含む)にのみ再読み込みされます。

履歴件数を変更するには、Assets/Photon/BotSDK/VisualEditor/CircuitScriptablesフォルダーのSettingsDatabaseという名前のアセットを選択して、そこにあるSave History Countフィールドを変更してください。

History Count
Back to top