UE4 アニメ―ションBPをC++クラス継承にしてアクセスする

# 概要
UnrealEngine のアニメーションブループリントをC++クラス継承する形にして処理を移譲できるようにしてみます。
アニムグラフのステートマシン遷移ルールでの呼び出しもテストしてみます。

## 修正履歴
|日付|内容|
|:---------|:---------|
|2019/08/28|C++での各情報へのアクセスについて追記|
|2019/09/05|アニメ―ションシーケンスの通知について追記|

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.22

## 参考
以下を参考にさせて頂きました。
https://wiki.unrealengine.com/Animation_Blueprint,_Set_Custom_Variables_Via_C%2B%2B
[UE4ドキュメント-遷移ルール](https://api.unrealengine.com/JPN/Engine/Animation/StateMachines/TransitionRules/index.html)

# 実装

## アニメーションBPの作成
対象のスケルトンを右クリック -> [作成する] -> [Animブループリント]

例：ThirdPersion_AnimBP.uasset
![animBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/59d3dde4-d703-dbcf-f6a7-4ef002e17110.png)



## C++クラスの作成
UAnimInstanceを継承したC++クラスを作成をします。
コンテンツブラウザにて、[新規追加] -> [新規C++クラス...]

▼ソースファイルパス
"Engine\Source\Runtime\Engine\Classes\Animation\AnimInstance.h"

![ParentClass_AnimInstance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/97e194ee-f513-8bed-92ba-40ee4d720600.png)



オーバーライド可能なメソッドをいくつか実装しています。

```cpp:MyAnimInstance.h
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "MyAnimInstance.generated.h"

/**
 * 
 */
UCLASS()
class TEST_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	

public:
	// コンストラクタ
	UMyAnimInstance(const FObjectInitializer& ObjectInitializer);

	// アニメーションの初期化時
	virtual void NativeInitializeAnimation() override;
	// アニメーションの更新時
	virtual void NativeUpdateAnimation(float DeltaTime) override;
	// アニメーション評価後
	virtual void NativePostEvaluateAnimation() override;
	// アニメーションの終了化時
	virtual void NativeUninitializeAnimation() override;

	// イベント開始時
	virtual void NativeBeginPlay() override;
};

```

cpp側です。メソッドの中身はとりあえずUE_LOG出力にしておきます。

```cpp:MyAnimInstance.cpp
#include "MyAnimInstance.h"


UMyAnimInstance::UMyAnimInstance(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{}

void UMyAnimInstance::NativeBeginPlay()
{
	Super::NativeBeginPlay();

	UE_LOG(LogTemp, Log, TEXT("UMyAnimInstance::NativeBeginPlay()"));
}

void UMyAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();

	UE_LOG(LogTemp, Log, TEXT("UMyAnimInstance::NativeInitializeAnimation()"));
}

void UMyAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
	Super::NativeUpdateAnimation(DeltaTime);

	UE_LOG(LogTemp, Log, TEXT("UMyAnimInstance::NativeUpdateAnimation()"));
}

void UMyAnimInstance::NativePostEvaluateAnimation()
{
	Super::NativePostEvaluateAnimation();

	UE_LOG(LogTemp, Log, TEXT("UMyAnimInstance::NativePostEvaluateAnimation()"));
}

void UMyAnimInstance::NativeUninitializeAnimation()
{
	Super::NativeUninitializeAnimation();

	UE_LOG(LogTemp, Log, TEXT("UMyAnimInstance::NativeUninitializeAnimation()"));
}

```

### 追記１：UAnimInstance の アニメーション関連系のC++メソッドについて
アニメーションブループリントにて使用される以下4つのメソッド
*BlueprintInitializeAnimation* 
*BlueprintUpdateAnimation*
*BlueprintPostEvaluateAnimation*
*BlueprintBeginPlay*
これらはオーバーライドできませんので、ブループリント専用です。

オーバーライド可能なメソッドは以下のようです。

```cpp:AnimInstance.h
virtual void NativeBeginPlay();
virtual void NativeInitializeAnimation();
virtual void NativeUpdateAnimation(float DeltaSeconds);
virtual void NativePostEvaluateAnimation();
virtual void NativeUninitializeAnimation();
```

NativeInitializeAnimationはアニメ―ション開始時、NativeUninitializeAnimationはアニメ―ション終了時、NativeUpdateAnimationとNativePostEvaluateAnimationは更新されるフレームのようです。

また、上のテストコードでは継承元のメソッドを実行していますが(Super::Native***())、継承元クラスUAnimInstanceの該当メソッドの処理は空なのでなくても（今のところは）問題はありません。ただ将来のバージョンではどうなるかわからないので呼んでおくほうが無難ではあります。

### 追記２：UAnimInstance のステートマシン関連系のC++メソッドについて
ステートマシンへのステートや遷移ルールの追加削除は以下のメソッドで行うことができるようです。

```cpp:AnimInstance.h
void AddNativeTransitionBinding(const FName& MachineName, const FName& PrevStateName, const FName& NextStateName, const FCanTakeTransition& NativeTransitionDelegate, const FName& TransitionName = NAME_None);
bool HasNativeTransitionBinding(const FName& MachineName, const FName& PrevStateName, const FName& NextStateName, FName& OutBindingName);
void AddNativeStateEntryBinding(const FName& MachineName, const FName& StateName, const FOnGraphStateChanged& NativeEnteredDelegate);
bool HasNativeStateEntryBinding(const FName& MachineName, const FName& StateName, FName& OutBindingName);
void AddNativeStateExitBinding(const FName& MachineName, const FName& StateName, const FOnGraphStateChanged& NativeExitedDelegate);
bool HasNativeStateExitBinding(const FName& MachineName, const FName& StateName, FName& OutBindingName);
```

ステートマシン自体の追加はUAnimInstanceクラスには見当たりません。


## 親クラスの差し替え
アニメーションブループリントの親クラスを差し替えます。

[ファイル] -> [ブループリントの親を変更]
![superclass.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/df352277-daec-2b95-dd48-90fd874e2fa5.png)

親クラス選択ウィンドウにて選ぶと、アニメーションBPウィンドウ右側の表示が変わり、親クラスが切り替わっていることがわかります。

![super_class_name.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/09340bee-71e6-ad3c-8d73-bbe182e090d6.png)

これで準備はできました。

# 実行テスト
## C++メソッド呼び出し確認
アニメ―ションを再生するとアウトプットログに各NativeメソッドのUE_LOGが出力されており、C++メソッドが呼び出されていることが確認できます。

![OutputLog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/270b4848-8b06-23a3-d273-5651fbf1b388.png)

1か所だけUpdateが呼ばれて、Evaluateが呼ばれない箇所がありますが、処理落ちでしょうか？


## ステートマシンからの呼び出し確認
ステートマシンの遷移ルール（トランジッション）から呼び出せるかテストしてみます。

以下のようなメソッドを追加します。

```cpp:MyAnimInstance.h
UFUNCTION(BlueprintPure)
	bool TestRule();
```

コンポーネントから情報を取得して結果を遷移ルールとしています。

```cpp:MyAnimInstance.cpp
bool UMyAnimInstance::TestRule()
{
	UE_LOG(LogTemp, Log, TEXT("MyTestRule!"));

	auto _Pawn = TryGetPawnOwner();
	if (_Pawn == nullptr)return(false);

	bool _IsFall = _Pawn->GetMovementComponent()->IsFalling();

	return(_IsFall);
}
```


ステートマシンです。
テストには[Idle/Run] から [JumpStart] への遷移部分を使います。
![StateMachine.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b4aa180c-1165-f1e6-862c-91345346eb7a.png)

[Idle/Run] から [JumpStart] へのトランジッションに上で追加した TestRuleメソッドを呼び出してみます。

![Transision.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/dc9d8aa1-1099-f3dc-a38b-263c0f6f1083.png)

以下のようなワーニングがでました。

```terminal:コンパイル結果
Node  Result  uses potentially thread-unsafe call  Test Rule . Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding. 
```

どうやらアニムグラフでのメソッドは明示的にスレッドセーフを宣言しないとメインスレッドで処理されるようです。

回避するにはスレッドセーフを宣言するか、イベントグラフにてメソッドを実行し（メインスレッドで実行し）結果を変数経由で受け取るしかないようです。

今回は特にメインスレッドと競合するような処理ではないので、スレッドセーフを宣言するようにメタ情報を修正してワーニングを回避します。

```cpp:MyAminInstance.h
UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe))
		bool TestRule();
```

コンパイル＆ホットリロード後、アニムグラフを確認するとワーニングがなくなりました。
![Transision2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2cfa118f-2459-7057-d1d8-aa5f85ab22d9.png)


実行すると、UE_LOGが大量にアウトプットログに表示されており、TestRuleメソッドが呼び出されていることが確認できます。
![TestRule_OutputLog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/18862b0f-2839-a769-ffca-f38094e43368.png)


# その他（AnimInstanceクラスからの各情報へのアクセス）

## ステート開始/離脱イベントをC++から登録する
ステートマシン"StateMachineName" のステート"StateName" のステート開始/離脱イベントの登録です。ただBPで指定するようなブレンド完了時のイベント登録メソッドが見当たりません。

```cpp:MyAnimInstance.cpp

void UMyAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();

	// 開始イベントをバインド
	AddNativeStateEntryBinding(FName(TEXT("StateMachineName")), FName(TEXT("StateName")), FOnGraphStateChanged::CreateUObject(this, &UMyAnimInstance::OnEntry));
	// 離脱イベントをバインド
	AddNativeStateExitBinding(FName(TEXT("StateMachineName")), FName(TEXT("StateName")), FOnGraphStateChanged::CreateUObject(this, &UMyAnimInstance::OnExit) );
}

void UMyAnimInstance::OnEntry(const struct FAnimNode_StateMachine& Machine, int32 PrevStateIndex, int32 NextStateIndex)
{
	UE_LOG(LogTemp, Log, TEXT("Entry"));
}
void UMyAnimInstance::OnExit(const struct FAnimNode_StateMachine& Machine, int32 PrevStateIndex, int32 NextStateIndex)
{
	UE_LOG(LogTemp, Log, TEXT("Exit"));
}

```

C++でのイベント登録は、BPでのステートイベントとは別にNative用なっているようなので、両方設定することができます。

### AddNativeStateEntryBindingでの注意点
以下のようなステート[Jump]に AddNativeStateEntryBinding で登録すると最初の1回目はバインドしたメソッドが呼ばれません。（AddNativeStateExitBindingは問題なく呼ばれます）
![StateMachine0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/17897071-e3df-1115-bf92-716517ffad48.png)

以下の様に[Entry]から１つステートを挟むようにして[Jump]にすると問題なく呼ばれます。
![StateMachine1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5a46033f-7246-9243-472e-c8fdf901ddf2.png)

ステートマシンの設定の[Skip First Update Transition]を false にしても変わりませんでした。Native登録の場合は効いていない感じです。:worried:
![SM_Setting.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/438b929a-13e8-678e-fa78-9bfa69127526.jpeg)


## ステートマシンへC++からアクセスする
初期化（AnimInstance::NativeInitializeAnimation()）の後にアクセスする必要があるようです。

```cpp
// ステートマシンのステータスとAnimNotifyを表示
const FBakedAnimationStateMachine* _AnimSM = GetStateMachineInstanceDesc(TEXT("StateMachineName"));
if (_AnimSM) {
	for (auto& _It : _AnimSM->States) {
		UE_LOG(LogTemp, Log, TEXT("%s, (%d,%d,%d)")
			, *_It.StateName.ToString()
			,_It.StartNotify
			,_It.EndNotify
			,_It.FullyBlendedNotify
		);
	}
}
```

Notifyは-1だと通知が割り振られていません。
また、トランジッションにもアクセス可能です。

## アニメ―ションシーケンスに割り当てられているAnimNotifyを調べる
アニメ―ションシーケンスに割り当てた通知名を出力するテストコードです。

```cpp:.cpp
// コンストラクタ
UAnimSequenceBase* _AnumSeq;
static ConstructorHelpers::FObjectFinder< UAnimSequenceBase > find_data(TEXT("AnimSequence'/Game/Chara/AnimSqWalk.AnimSqWalk'"));
if (find_data.Succeeded()) {
	_AnumSeq = find_data.Object;

	// 通知を調べる
	for (auto& _It : _AnumSeq->Notifies) {
		UE_LOG(LogTemp, Log, TEXT("%s"), *_It.GetNotifyEventName().ToString());
	}
}
```


# まとめ
アニメーションブループリントをC++クラスを継承する形にすると、複雑な処理をC++側に持って行ってメソッド化できるためアニメ―ションBPコードが少なくなると思います。（自分は特にBPがごちゃごちゃになりやすいので）
特に複雑なステートマシンなどを作る場合などは有用な手段ではないかと思います。（ただしクラス継承は深くなってしまいますが）