UE4 AnimNotifyをC++で書く場合のメモ

# 概要
UnrealEngine の AnimNotify についてのメモです。
主に AnimNotifyStateをC++で実装する場合の問題点と回避方法について記述しています。

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.25

## 参考
以下を参考にさせて頂きました、ありがとうございます。
[UE公式 : アニメーション通知](https://docs.unrealengine.com/ja/Engine/Animation/Sequences/Notifies/index.html)
[[UE4] 独自のAnimation Notifyの実装方法](https://historia.co.jp/archives/8643/)

# AnimNotify / AnimNotifyState とは
アニメ―ションシーケンスなどに埋め込んで任意のタイミングで何らかの処理を行うための通知システムです。

# 関連ソース
"Engine\Source\Runtime\Engine\Classes\Animation\AnimNotifies\AnimNotify.h"
"Engine\Source\Runtime\Engine\Classes\Animation\AnimNotifies\AnimNotifyState.h"


# BPで作成
ブループリントをAnimNotify か AnimNotifyStateを親として作成します。

AnimNotifyは `Received Notify`をオーバーライドして処理を作成します。
![AnimNotifyBP_override.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/87792f26-cec6-c5e6-950d-fe36c1def51b.jpeg)

AnimNotifyStateは `ReceivedNotifyBegin` `ReceivedNotifyEnd` `ReceivedNotifyTick`をオーバーライドして処理を作成します。
![AnimNotifyStateBP_override.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/23ee556f-56ec-1c9d-dc34-ac5f5c0c0bdc.jpeg)

## 作成例
`Received Notify` をオーバーライドして処理を作成する例です。
アクターに対してイベントを送っています。
![AnimNotiryBP_sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/144f7cdb-0536-9355-5059-84821b3a4a17.jpeg)



# C++で作成
C++にて、AnimNotify か AnimNotifyState を親クラスとして作成します。
BPの場合とはオーバーライドするメソッド名が違うことに注意。

## 作成例
以下、AnimNotifyStateを継承した自作アニメーション通知ステートのサンプルコードです。メンバ`Count`を追加してありますが<font color=red>そのままだと問題があります</font>。

```c++:AnimNotifyState_Test.h
#pragma once
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "AnimNotifyState_Test.generated.h"

UCLASS(editinlinenew, Blueprintable, const, hidecategories = Object, collapsecategories, meta = (ShowWorldContextPin, DisplayName = "Test"))
class TEST_API UAnimNotifyState_Test : public UAnimNotifyState
{
	GENERATED_UCLASS_BODY()

public:
	virtual void NotifyBegin(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float TotalDuration);
	virtual void NotifyEnd(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation);
	virtual void NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime);


#if WITH_EDITOR
	virtual bool CanBePlaced(UAnimSequenceBase* Animation) const override;
#endif

	// メンバを持つ（普通に使用するのは問題あり）
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UAnimNotifyState_Test")
	float	Count;
};
```

メンバ `Count`に対しての処理のみです。最後にアウトプット出力をしています。
普通はBegin/Endのタイミングでイベントを発行するような使い方が多いかと思います。

```cpp:AnimNotifyState_Test.cpp
#include "./AnimNotifyState_Test.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "Animation/AnimSequenceBase.h"
#include "Animation/AnimMontage.h"
#include "Animation/AnimInstance.h"

UAnimNotifyState_Test::UAnimNotifyState_Test(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

	Count = 0.0f;
}

void UAnimNotifyState_Test::NotifyBegin(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float _TotalDuration)
{
	Super::NotifyBegin(MeshComp, Animation, _TotalDuration);

	// 初期化
	Count = 0.0f;
}

void UAnimNotifyState_Test::NotifyEnd(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation)
{
	Super::NotifyEnd(MeshComp, Animation);


	// 結果表示
	UE_LOG(LogTemp, Log, TEXT("Id:%d, Cound:%f"), MeshComp->GetOwner()->GetUniqueID(), Count);
}

void UAnimNotifyState_Test::NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime)
{
	Super::NotifyTick(MeshComp, Animation, FrameDeltaTime);
	
	// カウント
	Count += FrameDeltaTime;
}

#if WITH_EDITOR
bool UAnimNotifyState_Test::CanBePlaced(UAnimSequenceBase* Animation) const
{
	if( Animation ){
		if( Animation->IsA(UAnimSequence::StaticClass())
			|| Animation->IsA(UAnimMontage::StaticClass())
		){
			return(true);
		}
	}
	
	return(false);
}
#endif // WITH_EDITOR

```


### CanBePlaced について
アニメ―ションシーケンスのみやモンタージュのみといったように通知を配置できる条件を設定できます。

```cpp:ファイル名で配置条件を指定する例
bool UAnimNotify_Test::CanBePlaced(UAnimSequenceBase* Animation) const
{
	if( Animation->GetName().Equals(TEXT("TestAnimSeq")) ){
		return(true);
	}
	return(false);
}
```


## 通知の設定
任意のアニメ―ションシーケンスアセットを開いて`通知`から右クリック `通知ステートを追加` で先ほど作成した通知を追加します。
詳細をみると追加したメンバ `Count` が確認できます。

![setting_Notify.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/dc9432d7-3695-2b92-5324-3897f68563af.jpeg)

これで通知範囲にアニメ―ションが入った場合、`NotifyBegin` が呼ばれ、通知範囲からアニメ―ションが出た場合、`NotifyEnd` が呼ばれ、通知範囲内で `NotifyTick` が呼ばれるようになります。


## 実行結果
任意のアクターがこのアニメーションを再生するとアウトプットログは以下の様になります。処理が呼ばれることを確認できます。

```console:アウトプットログ
LogTemp: Id:165160, Cound:0.783333
```

**UROなどのアニメ―ション最適化処理をしているとカメラとの距離などにより数値が変わる場合があります。**


## 上記のサンプルコードの問題点について
上とは別なアクターがこのアニメーションを再生するとアウトプットログは以下の様になりました。

```console:アウトプットログ
LogTemp: Id:165102, Cound:0.783333
```

ユニークIDが違うので別アクターから呼び出されたことがわかります。Tickで回しているカウンタの数値は一緒です。
これを2体のアクターがほぼ同時に呼ぶと以下のような結果になりました。

```console:アウトプットログ
LogTemp: Id:165160, Cound:1.449999
LogTemp: Id:165102, Cound:1.499999
```

数値がおかしくなりました。
これは `UAnimNotifyState` は `UCLASS(const)` で定義されたクラスでインスタンス毎ではなく同じオブジェクトを再利用しているためです。
この回避方法としては、

1.TMapなどの配列で外部から変数を退避&書き戻しを行う
2.エンジンに実装されている `UAnimNotifyState_TimedParticleEffect` クラスのような処理を行う(パーティクル名とソケット名などで再検索をしています)

のような方法が考えられます。
この const クラスは AnimNotifyState だけではなく、AnimNotify でも同様ですが、こちらはワンショットなため発覚しにくいと思います。こちらも可能ならメンバ変数を外部へ退避できる仕組みを作成するのが好ましいと思います。


## 回避コード例
`Owner` になるクラスに 

```cpp:オーナーアクタークラス
TMap<int32, UAnimNotifyState*> AnimNotifyStateMap;
```

のような TMap を作成します。
次に以下の様に変数を退避/書き戻しを行います。`Copy`はメンバのコピーメソッドです。


```cpp:AnimNotifyState_Test.cpp

void UAnimNotifyState_Test::NotifyBegin(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float _TotalDuration)
{
	Super::NotifyBegin(MeshComp, Animation, _TotalDuration);

	Count = 0.0f;

	auto _Owner = MeshComp->GetOwner();
	if(::IsValid(_Owner)){
	auto _Object = NewObject<UAnimNotifyState_Test>();
		Copy(_Object, this);
		// TMAPへ追加
		_Owner->AnimNotifyStateMap.Add(Animation->GetUniqueID(), _Object);
	}

}

void UAnimNotifyState_Test::NotifyEnd(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation)
{
	Super::NotifyEnd(MeshComp, Animation);

	auto _Owner = MeshComp->GetOwner();

	// 変数を書き戻し
	if(::IsValid(_Owner)){
		UAnimNotifyState* _Object = *_Owner->AnimNotifyStateMap.Find(Animation->GetUniqueID());
		if( ::IsValid(_Object)){
			Copy(this, Cast<UAnimNotifyState_Test>(_Object));
		}
	}

	if(::IsValid(_Owner)){
		// TMAPから削除
		_Owner->AnimNotifyStateMap.Remove(Animation->GetUniqueID());
	}

	// 結果表示
	UE_LOG(LogTemp, Log, TEXT("Id:%d, Cound:%f"), MeshComp->GetOwner()->GetUniqueID(), Count);
}

void UAnimNotifyState_Test::NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime)
{
	Super::NotifyTick(MeshComp, Animation, FrameDeltaTime);
	
	auto _Owner = MeshComp->GetOwner();

	// 変数を書き戻し
	if(::IsValid(_Owner)){
		UAnimNotifyState* _Object = *_Owner->AnimNotifyStateMap.Find(Animation->GetUniqueID());
		if( ::IsValid(_Object)){
			Copy(this, Cast<UAnimNotifyState_Test>(_Object));
		}
	}

	Count += FrameDeltaTime;

	// 退避
	if(::IsValid(_Owner)){
		UAnimNotifyState* _Object = *_Owner->AnimNotifyStateMap.Find(Animation->GetUniqueID());
		if( ::IsValid(_Object)){
			Copy(Cast<UAnimNotifyState_Test>(_Object), this);
		}	
	}
}

```

これで回避は一応できます。
ですが、これにも問題があります。１つのアニメ―ションシーケンスに複数通知を置くと検索で違うものを取得する場合があります。（検索Keyがアニメ―ションのユニークIDなため）
使用方法に合わせて回避コードを組む必要があります。


# まとめ
もっと良い回避方法があればどなたがご教授下さい。
というかアニメ―ション通知の仕様がインスタンス毎に作成されるようになってほしいです。