UE4 イベント処理(デリゲート)についてのメモ

# 概要
UnrealEngine4でのイベント処理（デリゲート）とその関連処理についてのメモ書きです。タイマー処理についても書いています。
ブループリントでのイベント処理をUnrealC++で実装する場合の検証も行っています。


## 修正履歴
| 日付|内容|
|:---|:---|
|2019/08/03|BlueprintImplementableEventについて追記|
|2019/08/04|タイマー処理について追記|
|2019/10/18|TFunction<>宣言とラムダ式の引き数型違い時について追記|
|2019/12/09|タイマーハンドルのIsVaild()についての追記|
|2020/11/27|Weak参照のラムダ(BindWeakLambda)についての追記|

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.22, 4.25

## 参考
[UnrealC++のデリゲート、イベントについて](http://miyahuji111.hatenablog.com/entry/2017/04/06/155420)
[Unrealマニュアル-デリゲート](https://docs.unrealengine.com/ja/Programming/UnrealArchitecture/Delegates/index.html)
[UE公式:TBaseDelegate::BindWeakLambda](https://docs.unrealengine.com/en-US/API/Runtime/Core/Delegates/TBaseDelegate/BindWeakLambda/index.html)
[【UE4 C++】デリゲートの使い方まとめ](https://dokuro.moe/ue4-cpp-how-to-use-delegate/)
[UE4|IUE5 Delegate, EventDispatcherをC++で書く方法](https://papersloth.hatenablog.com/entry/2023/04/20/201947)

# テスト１：SetTimerByEvent
タイマー処理をする SetTimerByEvent を使ってのメソッド呼び出しです。

### Unreal C++側実装
Test.cppにて呼び出されるメソッドを実装します。
"TestEvent!" をログに表示されるだけのものです。

```cpp:Test.h

	UFUNCTION(BlueprintCallable, Category = "Test")
		void TestEvent() { UE_LOG(LogTemp, Log, TEXT("TestEvent!")); }
```

### BluePrint側実装
Test.cppを継承したブループリントBP_Test.uassetにて以下の様に実装します。
Timeはとりあえず3.0、ループはonにしてみます。

![TestEventBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/81d1f142-444d-c235-3124-41c577feb546.png)


上記BPをUnrealC++で書くと以下の様になります。
*BindUFunction()* の第2引数にてデリゲートにバインドするメソッドを文字列指定していますが、Bind系メソッドはラムダ式を使うものなど他にいろいろあるようです。

```cpp:Test.cpp
// コンストラクタ
ATest::ATest(){
	FTimerDynamicDelegate _Delegate;
	_Delegate.BindUFunction(this, "TestEvent");
	UKismetSystemLibrary::K2_SetTimerDelegate(_Delegate, 3.0f, true);
}
```


### 結果
3秒ごとに **[TestEvent!]** がアウトプットログに表示されます。

![outputlog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/e65fdf44-0957-ac82-4ffe-bcee91994567.png)


### 追記１：タイマー処理の削除
タイマー処理の削除は `UKismetSystemLibrary::K2_SetTimerDelegate()` の返り値 `FTimerHandle` を保持して以下の様に `K2_ClearAndInvalidateTimerHandle` を呼ぶことにより実装できます。

```cpp
// タイマー処理の削除
UKismetSystemLibrary::K2_ClearAndInvalidateTimerHandle(GetWorld(), TimerHandle);

```

### 追記２：C++のみでタイマー処理を行う例
タイマー処理をC++のみで行う場合は、ラムダ式で指定をすると便利です。

```cpp
// 任意時間後に実行する処理
TFunction<void(void)> _Func = [this]() {
	UE_LOG(LogTemp, Log, TEXT("Test!"));
}; 

FTimerHandle _Handle;
// 1秒後にセット
GetWorld()->GetTimerManager().SetTimer(_Handle, (TFunction<void(void)>&&)_Func, 1.0f, false);
	
```

上記のテストコードは`FTimerHandle` をローカル変数で使っていますが、メンバ等などで保持＆使いまわしをする場合は、`Invalidate()` で初期化と `IsVaild()`で使用中かどうかの確認をすると良いです。間違って踏みつぶすとタイマー処理が実行されません。:cry:

また、引き数がある場合は、`FTimerDelegate`を使います。

```cpp
FTimerHandle	_Handle;
FTimerDelegate	_TimerDelegate;

// 任意時間後に実行する処理（引数あり）
TFunction<void(float)> _Func = [this](float _Val) {
	UE_LOG(LogTemp, Log, TEXT("Test : %f"), _Val);
};
_TimerDelegate.BindLambda((TFunction<void(float)>&&)_Func, 1.0f);
// 2秒後にセット
GetWorld()->GetTimerManager().SetTimer(_Handle, _TimerDelegate, 2.0f, false);

```

ラムダ式ではなくメソッドを指定する場合はバインドするメソッド名が違うので注意。

```cpp
// 同一クラスのメソッドTestFuncをデリゲートにバインドする場合
_TimerDelegate.BindUFunction(this, FName("TestFunc"), 1.0f);
```

また `BindWeakLambda` や `CreateWeakLambda` など弱参照なオブジェクトをバインドできるメソッドもあります。第1引数が違うので注意。

```cpp
_TimerDelegate.BindWeakLambda(this, (TFunction<void(float)>&&)_Func, 1.0f);
```


### 追記３：コンパイルエラーが出ないケース
`TFunction<>`での宣言と実際のラムダ式の引数の型が違う場合、なぜかコンパイルエラーがでません。
下記のコードの場合、1秒後に[Param:100]と出力されると期待されますが、実際は[Param:0]となってしまいます。

```cpp
TFunction<void(float)> Func = [this](int32 _Param) {    // 引数の型が宣言と違う
	UE_LOG(LogTemp, Log, TEXT("Param:%d"), _Param);
};
TimerDelegate.BindLambda((TFunction<void(int32)>&&)Func, 100);

GetWorld()->GetTimerManager().SetTimer(TimerHandle, TimerDelegate, 1.0f, false);
```



# テスト２:コンポーネントのヒットイベント
カプセルコンポーネントを持つアクターの `OnComponentHit` イベントです。コリジョンを持つコンポーネントのヒットイベント系の処理は全て同じだと思います。

## UnrealC++側イベント実装
"OnHit!"を表示されるだけの実装です。

```cpp:ATest.h
	UFUNCTION(BlueprintCallable, Category = "Test")
		void OnCompHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

```

```cpp:ATest.cpp
void ATest::OnCompHit(
		UPrimitiveComponent* HitComp,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		FVector NormalImpulse,
		const FHitResult& Hit
		)
{
	UE_LOG(LogTemp, Log, TEXT("OnHit!"));
}
```

## BluePrint側実装
テストコードでは引数使っていませんが一応全部つなぎます。

![OnCompHitBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/64cb4e7c-c9da-ba7a-bb5b-2cc524e77292.png)


上記をUnrealC++側で書くと

```cpp:ATest.cpp
void ATest::PostInitializeComponents(){
	CapsuleComponent->OnComponentHit.AddDynamic(this, &ATest::OnCompHit);
}
```
コンポーネントヒット用デリゲートに追加しています。


## 結果
カプセルコリジョンヒット時に [**OnHit!**] がアウトプットログに表示されます。


# テスト３：動的マルチキャストデリゲート
Unreal用語的には **イベントディスパッチャー**
メソッドを２つ登録して呼び出ししてみます。

## UnrealC++側処理

```cpp:ATest.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTestDelegate);

UCLASS()
class TEST_API ATest : public APawn
{
	GENERATED_BODY()
public:

	// 呼び出されるメソッド１
	UFUNCTION(BlueprintCallable, Category = "Test")
		void TestEvent(){ UE_LOG(LogTemp, Log, TEXT("TestEvent!")); }
	// 呼び出されるメソッド２
	UFUNCTION(BlueprintCallable, Category = "Test")
		void TestEvent2() { UE_LOG(LogTemp, Log, TEXT("TestEvent2!")); }

	// デリゲート定義
	UPROPERTY(BlueprintAssignable, Category = "Test")
	FTestDelegate TestDelegate;

	// 呼び出し
	UFUNCTION(BlueprintCallable, Category = "Test")
		void CallDelegate();

};	
```

```cpp:ATest.cpp

void ATest::BeginPlay()
{
	Super::BeginPlay();

	// 追加
	TestDelegate.AddDynamic(this, &ATest::TestEvent);
	TestDelegate.AddDynamic(this, &ATest::TestEvent2);
}

void ATest::CallDelegate() {
	// 呼び出し
	TestDelegate.Broadcast();
}

```

## BluePrint側実装
[z]キーを押して **[CallDelegate]**を呼び出す処理にしてみます。

![call_delegate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/df871fa2-02c1-d1a6-05d7-32a83447388f.png)


## 結果
マルチキャストなので、**[TestEvent!]** と **[TestEvent2]** がアウトプットログに表示されます。

![result3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/ccb0452d-7f80-dce1-2023-7e64a1c5bc22.png)


## 追記
引き数が必要な場合は、以下の様に引き数の数に応じて `_OneParam` や `_TwoParams` を追加するようです。(2つ以上はParam **s** になることに注意)

```cpp
// int型の変数名valueの引数1つを取る場合
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTestDelegate, int, value);
```
呼び出し側は以下の様になります。

```cpp
// デリゲート呼び出し（int型引き数を1つ）
int InParam1 = 10;
TestDelegate.Broadcast(InParam1);
```

宣言時に引数の型(int)だけではなく、変数名(value)まで書く必要があるのはブループリント側でのイベントバインド時の表示をするためのようです。
![delegate_oneparam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/af8c39f3-45f5-b7de-24e3-4502aa56be61.png)

ブループリントに公開しないデリゲート（Dynamicではないデリゲート）の宣言は変数名は不要です。

```ATest.h
// int引数１つ取るBP非公開デリゲート
DECLARE_DELEGATE_OneParam(FNoBPEvent, int);
```


# テスト４：イベント
Unreal用語的には **動的デリゲート**
これもメソッドを２つ登録して呼び出ししてみます。

## UnrealC++側実装

```cpp:ATest.h
DECLARE_DYNAMIC_DELEGATE(FTestOnEvent);

UCLASS()
class TEST_API ATest : public APawn
{
	GENERATED_BODY()
public:

	// 呼び出されるメソッド１
	UFUNCTION(BlueprintCallable, Category = "Test")
		void TestEvent(){ UE_LOG(LogTemp, Log, TEXT("TestEvent!")); }
	// 呼び出されるメソッド２
	UFUNCTION(BlueprintCallable, Category = "Test")
		void TestEvent2() { UE_LOG(LogTemp, Log, TEXT("TestEvent2!")); }

	// イベント定義
	UPROPERTY(BlueprintReadWrite, Category = "Test")
		FTestOnEvent TestOnEvent;

	// イベント呼び出し
	UFUNCTION(BlueprintCallable, Category = "Test")
	void CallOnEvent();
};
```

```cpp:ATest.cpp
void ATest::BeginPlay()
{
	Super::BeginPlay();

	// 追加
	TestOnEvent.BindUFunction(this, "TestEvent");
	TestOnEvent.BindUFunction(this, "TestEvent2");
}

void ATest::CallOnEvent() {
	// 呼び出し
	TestOnEvent.ExecuteIfBound();
}

```
マルチキャストデリゲートの`Broadcast()`と違い、`Execute()`や`ExecuteIfBound()`はブループリント側からは<font color="red">直接は呼び出せない</font>ようです。

## Blueprint側実装

![callonevent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/6670e757-af96-6fbb-7559-0d462c706759.png)


## 結果
イベントはシングルキャストなので2つバインドしていますが、**[TestEvent2!]**だけ（最後にバインドした処理だけ）アウトプットログに表示されます。

![result4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2aa0ea87-4cbb-ae9f-4250-117726c85a53.png)

## 追記
マルチキャストデリゲートと同様に引き数を取る場合は、宣言時に *_OneParam* や *_TwoParams* を追加して、型と変数名を追加していきます。

```cpp:定義例
// 引数２つ、int型のvalue1とfloat型value2を取る場合
DECLARE_DYNAMIC_DELEGATE_TwoParams(FTestOnEvent, int, value1, float, value2);
```

返値がある場合の定義は *_RetVal* が後ろにつきます。更に引数が付く場合は *_OneParam* などを続けます。

```cpp:定義例
// 返値型int、引数なしのデリゲート定義
DECLARE_DYNAMIC_DELEGATE_RetVal(int, FTestDelegate);
// 返値型int, 引数１つ（float型ValName）のデリゲート定義
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(int, FTestDelegate2, float, ValName);
```

~~この時、なぜか呼び出しメソッドの ExecuteIfBound() がないので IsBound() で確認してから Execute() で呼び出しをしないとなりません。~~
勘違いでした。普通に`ExecuteIfBound()`が使えます。


# テスト５：BlueprintImplementableEvent
BlueprintImplementableEventを使うとC++側で宣言を行い実装はBP側で、呼び出しはC++で行うことができます。
内部的にはイベントで行われています。

```cpp:ATest.h
// 実装はBP側でC++では宣言のみ
UFUNCTION(Category = "Test", BlueprintImplementableEvent, BlueprintCallable)
	void TestImpl(float _DeltaTime);
```

BP側では以下のように処理を実装できます。
![ImplementableEvent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0c85a6bb-bf1d-9092-5b8f-c404a8423edf.png)

BPが継承関係になっている場合、親クラスの実装も呼ぶことができます。
![ParentImplementableEvent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/91001a3a-666c-59a5-f71d-1097fe773ff0.png)

この方法は実装や流れがシンプルなのでわかりやすいです。



# まとめ
用語が **とても** 紛らわしい。。
C#の delegate も変数を外部からのアクセスを制限するために event キーワードによる呼び出しがあるように、UnrealEngineのイベントディスパッチャーと動的デリゲートもそのような関係にあるのではないかと思います。

あと基本的にブループリントでの都合を重視しているためUnrealC++のみで考えると不要な実装も多いため混乱します。(`DECLARE_DYNAMIC_DELEGATE_OneParam`の宣言など)
UnrealC++だけで使用する場合は、（多分）動的デリゲートのほうが処理がかかると思いますので、通常デリゲート（静的デリゲート？）も使い分けたほうがいいと思われます。:worried: