UE5 StateTree についてのメモ

# 概要
UnrealEngine5 から追加された StateTree のメモです。ビヘイビアツリーのようにAIに使用したりできます。
タスクなどは毎フレーム処理があるのでC++で作成すると処理負荷の軽減につながります。


## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/06/22| 初版 |

## 参考
以下の記事を参考にいたしました、ありがとうございます。
[UE公式：StateTree概要](https://docs.unrealengine.com/5.1/ja/overview-of-state-tree-in-unreal-engine/)
[[UE5] StateTreeで状態管理](https://historia.co.jp/archives/34074/)

## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.1.1

## 関連ソース
"Engine\Plugins\Runtime\StateTree\Source\StateTreeModule\Public\StateTree.h"
"Engine\Plugins\Runtime\StateTree\Source\StateTreeModule\Public\StateTreeTaskBase.h"
"Engine\Plugins\Runtime\StateTree\Source\StateTreeModule\Public\StateTreeEvaluatorBase.h"
"Engine\Plugins\Runtime\StateTree\Source\StateTreeModule\Public\StateTreeConditionBase.h"
"Engine\Plugins\Runtime\GameplayStateTree\Source\GameplayStateTreeModule\Public\Components\StateTreeComponent.h"
"Engine\Plugins\Runtime\StateTree\Source\StateTreeModule\Public\StateTreeExecutionContext.h"


# 準備
プラグインから `GameStateTree` と `StateTree` の2つを有効にします。
![Plugin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/eed5b1b3-bdad-87c4-771f-1be99401eb78.png)

アセットブラウザ右クリック [AI] -> [StateTree] からアセットが作成可能になります。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/81a35ac9-2228-1f49-e39e-545d2eadd5ba.png">


C++で扱う場合は、`StateTreeModule` モジュールの追加を行います。

```cs:MyProject.Build.cs
using UnrealBuildTool;

public class StateTreeTest : ModuleRules
{
	public StateTreeTest(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] {
			"Core", 
			"CoreUObject", 
			"Engine", 
			"InputCore", 
			"HeadMountedDisplay", 
			"EnhancedInput",
			"StateTreeModule"      // ←追加
			});
	}
}
```


# ステートツリーの条件チェックの流れ
基本的に[Root]から始まって子ステートの条件チェックを行い条件に合わない場合、次の兄弟ステートの条件を見ていきます。

:::note info
下のスクショのようなステートツリーの場合、[State1]の条件チェック -> 条件に合わない場合[State2]の条件チェック... となります。
:::

条件に合ったステートの子ステートが更にあった場合、その条件を上から順に辿っていき、合致したものがあればそのステートをアクティブとするようです。子ステートの条件が全て合致しなかった場合、親ステートの条件が合致していない場合と同様に次の判定へ移ります。

:::note info
下のスクショの場合、[State1]に条件が合致した場合、子ステートの[State1-1]の条件を見ていきます。ここで[State1-1][State1-2]ともに条件を満たさない場合、次の[State2]の条件チェックへ移ります。
:::

![StateTree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/3ffbb91a-e574-86ab-525c-f9375058f882.png)

## 起動時ステートの注意点
ステートツリー起動時に条件に合うステートがない場合、以下のようなエラーログが出力されます。

```text:エラーログ
LogStateTree: Error: TextObject: FStateTreeExecutionContext::Start: Failed to select initial state on 'TestObject' using StateTree 'StateTree /Game/TestStateTree.TestStateTree'. This should not happen, check that the StateTree logic can always select a state at start.
```

この場合、ステートツリーの動作が止まります。なので最低限[Root]ステートの子の一番下は条件なしのステートを用意するといいと思います。


# StateTreeComponentについて
ビヘイビアツリーと同様に `BrainComponent`を継承しているので同じメソッドが使えます。

```cpp:StateTreeComponent.h
UCLASS(ClassGroup = AI, HideCategories = (Activation, Collision), meta = (BlueprintSpawnableComponent))
class GAMEPLAYSTATETREEMODULE_API UStateTreeComponent : public UBrainComponent, public IGameplayTaskOwnerInterface
{
	GENERATED_BODY()
// ...省略...

	// BEGIN UBrainComponent overrides
	virtual void StartLogic() override;
	virtual void RestartLogic() override;
	virtual void StopLogic(const FString& Reason)  override;
	virtual void Cleanup() override;
	virtual void PauseLogic(const FString& Reason) override;
	virtual EAILogicResuming::Type ResumeLogic(const FString& Reason)  override;
	virtual bool IsRunning() const override;
	virtual bool IsPaused() const override;
	// END UBrainComponent overrides

// ...省略...
};
```


特に`StopLogic`や`RestartLogic`で適宜処理停止/再開をすると負荷軽減になるので常駐するような利用の場合はやっておくべきかと思います。
![BrainComponent_StopLogic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0e5917e1-c24d-20ac-9244-b9b291bfcf34.png)



# C++でのタスク作成
C++でStateTree用タスクを作成するには以下2パターンあります。
どちらも `EnterState`がタスク開始時、`ExitState`がタスク終了時、`Tick`がタスク処理時の毎フレーム処理、`StateCompleted`がタスク完了時（完了せずに遷移した場合呼ばれない）となっています。
また`Tick`処理は返値によってタスク実行の継続or完了が決まります。

## UStateTreeTaskBlueprintBase を継承してタスクを作る
BPでタスクを作るのと同様に `UStateTreeTaskBlueprintBase` を継承してタスククラスを作成できます。
以下コード例。

```cpp:STT_Test.h
#include "CoreMinimal.h"
#include "StateTreeTaskBase.h"
#include "Blueprint/StateTreeTaskBlueprintBase.h"
#include "STT_Test.generated.h"

UCLASS(meta = (DisplayName = "MySTT_Test"))
class MYPROJECT_API USTT_Test : public UStateTreeTaskBlueprintBase
{
	GENERATED_BODY()

public:
	virtual EStateTreeRunStatus Tick(FStateTreeExecutionContext& Context, const float DeltaTime) override;

	virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition);
	virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) override;

	UPROPERTY(EditAnywhere, Category = Parameter, meta = (ToolTip = "Test"))
		int32  ParamInt = 0;
};
```

```cpp:STT_Test.cpp
#include "STT_Test.h"
#include "GameFramework/Actor.h"
#include "StateTreeExecutionContext.h"

// Tick処理
EStateTreeRunStatus USTT_Test::Tick(FStateTreeExecutionContext& Context, const float DeltaTime)
{
	if (bHasTick)
	{
		return ReceiveTick(DeltaTime);
	}
	return EStateTreeRunStatus::Running;
}

// タスク開始時処理
EStateTreeRunStatus USTT_Test::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition)
{
	Super::EnterState(Context, Transition);

	return EStateTreeRunStatus::Running;
}

// タスク終了時処理
void USTT_Test::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition)
{
	Super::ExitState(Context, Transition);

	auto _Owner = Context.GetOwner();
	if (_Owner) {
		auto _Actor = Cast<AActor>(_Owner);
		// 何らかの処理を行う
	}

}
```

結果は以下のようなタスクが作られます。
![STT_Test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/98a1153e-0d16-daf4-929e-897f32af848e.png)


## FStateTreeTaskCommonBase を継承してタスクを作る
`FStateTreeTaskCommonBase` を継承してタスクを作成することもできます。注意点としてはUObjectではないことと、公開パラメータやワークのためのデータを別に用意する必要があることです。
以下コード例。

```cpp:STT_Test2.h
USTRUCT()
struct MYPROJECT_API FSTT_Test2InstanceData
{
	GENERATED_BODY()
	
	UPROPERTY(EditAnywhere, Category = Parameter)
	bool bBoolParam = false;

	float RemainingTime = 1.f;
};

USTRUCT(meta = (DisplayName = "MySTT_Test2"))
struct MYPROJECT_API FSTT_Test2 : public FStateTreeTaskCommonBase
{
	GENERATED_BODY()

	typedef FSTT_Test2InstanceData InstanceDataType;
	
	FSTT_Test2() = default;

	virtual const UStruct* GetInstanceDataType() const override { return InstanceDataType::StaticStruct(); }

	virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override;
	virtual EStateTreeRunStatus Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const override;
	virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override;

};
```

```cpp:STT_Test2.cpp
// タスク開始時処理
EStateTreeRunStatus FSTT_Test2::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
{
	InstanceDataType& InstanceData = Context.GetInstanceData<InstanceDataType>(*this);

	return EStateTreeRunStatus::Running;
}

// Tick処理
EStateTreeRunStatus FSTT_Test2::Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const
{
	InstanceDataType& InstanceData = Context.GetInstanceData<InstanceDataType>(*this);

	InstanceData.RemainingTime -= DeltaTime;
	if (InstanceData.RemainingTime <= 0.f)
	{
		// 何らかの処理、完了時に Succeeded か Failed を返す
		return EStateTreeRunStatus::Succeeded;
	}
	
	return EStateTreeRunStatus::Running;
}

// タスク終了時処理
void FSTT_Test2::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
{
	InstanceDataType& InstanceData = Context.GetInstanceData<InstanceDataType>(*this);
}
```

結果以下のようなタスクが作られます。
![STT_Test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/576ede9e-156a-a0e0-21a3-048485bb2ef1.png)



# C++でのエバリュエーター作成
エバリュエーター作成も2パターンの作成方法があります。
どちらもステートツリー開始時処理`TreeStart`、ステートツリー終了時処理`TreeStop`、毎フレーム処理`Tick`が継承可能です。

## UStateTreeEvaluatorBlueprintBase を継承する
`UStateTreeEvaluatorBlueprintBase`を継承してエバリュエーター作成することができます。
以下コード例。

```cpp:STE_Test.h
#include "CoreMinimal.h"
#include "Blueprint/StateTreeEvaluatorBlueprintBase.h"
#include "StateTreeEvaluatorBase.h"
#include "STE_Test.generated.h"

UCLASS()
class MYPROJECT_API USTE_Test : public UStateTreeEvaluatorBlueprintBase
{
	GENERATED_BODY()

public:
	virtual void TreeStart(FStateTreeExecutionContext& Context) override;
	virtual void TreeStop(FStateTreeExecutionContext& Context) override;
	virtual void Tick(FStateTreeExecutionContext& Context, const float DeltaTime) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = OutPut)
	FVector OwnerLocation;

	AActor* Owner;
};

```

```cpp:STE_Test.cpp
#include "STE_Test.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Character.h"
#include "StateTreeExecutionContext.h"

// ステートツリー開始時処理
void USTE_Test::TreeStart(FStateTreeExecutionContext& Context)
{
	Super::TreeStart(Context);
	// 毎回キャストが無駄なので予めオーナーを取得しておく
	Owner = Cast<AActor>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
}

// ステートツリー終了時処理
void USTE_Test::TreeStop(FStateTreeExecutionContext& Context)
{
	Super::TreeStop(Context);
}

// Tick処理
void USTE_Test::Tick(FStateTreeExecutionContext& Context, const float DeltaTime)
{
	Super::Tick(Context, DeltaTime);
	// オーナーから値を取得
	if (Owner) {
		OwnerLocation = Owner->GetActorLocation();
	}
}
```


結果は以下のようにVector型プロパティを取得できるようになります。
![STE_Test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c70beac6-5e11-5bad-e235-252d800a8490.png)



## FStateTreeEvaluatorCommonBase を継承する
`FStateTreeEvaluatorCommonBase` を継承してエバリュエーター作成することができます。この場合はインスタンスデータは別途定義が必要です。
以下コード例。

```cpp:STE_Test2.h
// インスタンスデータ
USTRUCT()
struct MYPROJECT_API FSTE_Test2InstanceData
{
	GENERATED_BODY()
	
	UPROPERTY(EditAnywhere, Category = Parameter)
	int32 IntParam = 0;

	UPROPERTY(EditAnywhere, Category = Parameter)
	float FloatParam = 0.0f;

	AActor* Owner;
};

USTRUCT(meta = (DisplayName="MyEva2"))
struct MYPROJECT_API FSTE_Test2 : public FStateTreeEvaluatorCommonBase
{
	GENERATED_BODY()

	typedef FSTE_Test2InstanceData InstanceDataType;
	virtual const UStruct* GetInstanceDataType() const override { return InstanceDataType::StaticStruct(); }

	FSTE_Test2() = default;
	
	virtual void TreeStart(FStateTreeExecutionContext& Context) const override;
	virtual void TreeStop(FStateTreeExecutionContext& Context) const override;
	virtual void Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const override;
};
```

```cpp:STE_Test2.cpp
// 開始時処理
void FSTE_Test2::TreeStart(FStateTreeExecutionContext& Context) const
{
	InstanceDataType& InstanceData = Context.GetInstanceData<InstanceDataType>(*this);

	Super::TreeStart(Context);
	
	auto _World = Context.GetWorld();
	// 予めキャストして保持
	InstanceData.Owner = Cast<AActor>(UGameplayStatics::GetPlayerCharacter(_World, 0));

}

// 終了時処理
void FSTE_Test2::TreeStop(FStateTreeExecutionContext& Context) const
{
	Super::TreeStop(Context);

}

// Tick処理
void FSTE_Test2::Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const
{
	InstanceDataType& InstanceData = Context.GetInstanceData<InstanceDataType>(*this);

	Super::Tick(Context, DeltaTime);

	// オーナーから値を取得する例
	InstanceData.IntParam = Owner->GetActorLocation().X;
	InstanceData.FloatParam = Owner->GetActorLocation().Y;
}
```

結果は以下のようにInt型とFloat型プロパティを取得することができます。
![STE_Test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b9a38287-78b8-92a0-f0e8-4002be705bdb.png)



# C++でのコンディション作成
コンディション作成も2パターンあり、`TestCondition`で条件を判定するコードを書くことで作成ができます。

## UStateTreeConditionBlueprintBase を継承する
`UStateTreeConditionBlueprintBase`を継承してコンディションを作成することができます。
以下コード例。

```cpp:STC_Test.h
#include "CoreMinimal.h"
#include "Blueprint/StateTreeConditionBlueprintBase.h"
#include "STC_Test.generated.h"

UCLASS()
class MYPROJECT_API USTC_Test : public UStateTreeConditionBlueprintBase
{
	GENERATED_BODY()
	
protected:
	virtual bool TestCondition(FStateTreeExecutionContext& Context) const override;

	UPROPERTY(EditAnywhere, Category = Parameter)
	bool bInvert = false;
};
```

```cpp:STC_Test.cpp
#include "STC_Test.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Actor.h"
#include "StateTreeExecutionContext.h"
#include "StateTreePropertyBindings.h"

bool USTC_Test::TestCondition(FStateTreeExecutionContext& Context) const
{
	auto _Owner = Context.GetOwner();
	auto _Actor = Cast<AActor>(_Owner);
	bool _bResult = false;

	// 適当な条件
	if (_Actor && _Actor->GetActorScale().X < 1.0f){
		_bResult = true;
	}

	return _bResult ^ bInvert;
}
```

以下のようなコンディションになります。
![STC_Test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/90574bdc-6080-6258-bbba-74035684e952.png)



## FStateTreeConditionCommonBase を継承する
`FStateTreeConditionCommonBase`を継承することで作成ができます。例のごとくインスタンスデータは別途用意する必要があります。
以下コード例。

```cpp:STC_Test2.h
USTRUCT()
struct MYPROJECT_API FSTC_Test2InstanceData
{
	GENERATED_BODY()
	
	UPROPERTY(EditAnywhere, Category = Input)
	TObjectPtr<UObject> Object = nullptr;
};

USTRUCT(DisplayName="STC_Test2")
struct MYPROJECT_API FSTC_Test2 : public FStateTreeConditionCommonBase
{
	GENERATED_BODY()

	using FInstanceDataType = FSTC_Test2InstanceData;

	FSTC_Test2() = default;
	
	explicit FSTC_Test2(const EStateTreeCompare InInverts)
		: bInvert(InInverts == EStateTreeCompare::Invert)
	{}

	virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
	virtual bool TestCondition(FStateTreeExecutionContext& Context) const override;

	UPROPERTY(EditAnywhere, Category = Parameter)
	bool bInvert = false;
};
```

```cpp:STC_Test2.cpp
bool FSTC_Test2::TestCondition(FStateTreeExecutionContext& Context) const
{
	const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
	bool _bResult = false;

	// 適当な条件
	if(IsValid(InstanceData.Object) && InstanceData.Object.IsA(AActor::StaticClass()) ){
		_bResult = true;
	}

	return _bResult ^ bInvert;
}
```

以下のようなコンディションになります。
![STC_Test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/30ba4f9d-666f-602d-ac9d-4765ad9f49d9.png)


# その他
## デバッグ用メソッドについて
デバッグ用のメソッドは`FStateTreeExecutionContext` にあるようです。

```cpp:stateTreeExecutionContext.h
struct STATETREEMODULE_API FStateTreeExecutionContext
{
public:
// ..省略..

#if WITH_GAMEPLAY_DEBUGGER
	/** @return 現在の実行状態を説明するデバッグ文字列 */
	FString GetDebugInfoString() const;
#endif // WITH_GAMEPLAY_DEBUGGER

	/** @return アクティブな状態のステート名を返します */
	FString GetActiveStateName() const;
	
	/** @return 全てのアクティブな状態のステート名を返します */
	TArray<FName> GetActiveStateNames() const;
};
```


## UE5.2での変更について
5.1でプロダクション対応になったはずですが、変更点が大きいの注意が必要かと思われます。

### グローバルタスクの追加
グローバルタスクが追加されました。ROOTステートよりも速く処理が開始されるようです。ステートに関わらない永続的な処理が必要な場合に使うようです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b5e3a509-668a-d283-fbcf-e07ffa22c1b9.png">


### トランジッションの変更
`Transition To`から「遷移のブロック」がなくなり、`Priority`が追加されました。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/9febe36c-233d-c700-32a2-e883335537f5.png">


# まとめ
ビヘイビアツリーよりもシンプルかと思うのでAIに使うのもアリかと思いますが、AI以外にも使えそうな感じです。具体的にはイベントフロー管理などがいいのではないでしょうか。