Edited at

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


概要

UnrealEngine4でのイベント処理(デリゲート)とその関連処理についてのメモ書きです。

ブループリントでのイベント処理をUnrealC++で実装する場合の検証も行っています。


修正履歴

日付
内容

2019/08/03
BlueprintImplementableEventについて追記

2019/08/04
タイマー処理について追記


環境

Windows10

Visual Studio 2017

UnrealEngine 4.22


参考

UnrealC++のデリゲート、イベントについて

Unrealマニュアル-デリゲート


テスト1:SetTimerByEvent

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


Unreal C++側実装

Test.cppにて呼び出されるメソッドを実装します。

"TestEvent!" をログに表示されるだけのものです。


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

上記BPをUnrealC++で書くと以下の様になります。

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


Test.cpp

// コンストラクタ

ATest::ATest(){
FTimerDynamicDelegate _Delegate;
_Delegate.BindUFunction(this, "TestEvent");
UKismetSystemLibrary::K2_SetTimerDelegate(_Delegate, 3.0f, true);
}


結果

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

outputlog.png


追記1:タイマー処理の削除

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

// タイマー処理の削除

UKismetSystemLibrary::K2_ClearAndInvalidateTimerHandle(GetWorld(), TimerHandle);


追記2:C++のみでタイマー処理を行う例

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

// 任意時間後に実行する処理

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);

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

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);

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

// 同一クラスのメソッドTestFuncをデリゲートにバインドする場合

_TimerDelegate.BindUFunction(this, FName("TestFunc"), 1.0f);


テスト2:コンポーネントのヒットイベント

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


UnrealC++側イベント実装

"OnHit!"を表示されるだけの実装です。


ATest.h

    UFUNCTION(BlueprintCallable, Category = "Test")

void OnCompHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);



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

上記をUnrealC++側で書くと


ATest.cpp

void ATest::PostInitializeComponents(){

CapsuleComponent->OnComponentHit.AddDynamic(this, &ATest::OnCompHit);
}

コンポーネントヒット用デリゲートに追加しています。


結果

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


テスト3:動的マルチキャストデリゲート

Unreal用語的には イベントディスパッチャー

メソッドを2つ登録して呼び出ししてみます。


UnrealC++側処理


ATest.h

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTestDelegate);

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

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

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

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

};



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


結果

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

result3.png


追記

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

// int型の変数名valueの引数1つを取る場合

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTestDelegate, int, value);

呼び出し側は以下の様になります。

// デリゲート呼び出し(int型引き数を1つ)

int InParam1 = 10;
TestDelegate.Broadcast(InParam1);

宣言時に引数の型(int)だけではなく、変数名(value)まで書く必要があるのはブループリント側でのイベントバインド時の表示をするためのようです。

delegate_oneparam.png

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


ATest.h

// int引数1つ取るBP非公開デリゲート

DECLARE_DELEGATE_OneParam(FNoBPEvent, int);


テスト4:イベント

Unreal用語的には 動的デリゲート

これもメソッドを2つ登録して呼び出ししてみます。


UnrealC++側実装


ATest.h

DECLARE_DYNAMIC_DELEGATE(FTestOnEvent);

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

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

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

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



ATest.cpp

void ATest::BeginPlay()

{
Super::BeginPlay();

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

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


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


Blueprint側実装

callonevent.png


結果

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

result4.png


追記

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


定義例

// 引数2つ、int型のvalue1とfloat型value2を取る場合

DECLARE_DYNAMIC_DELEGATE_TwoParams(FTestOnEvent, int, value1, float, value2);

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


定義例

// 返値型int、引数なしのデリゲート定義

DECLARE_DYNAMIC_DELEGATE_RetVal(int, FTestDelegate);
// 返値型int, 引数1つ(float型ValName)のデリゲート定義
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(int, FTestDelegate2, float, ValName);

この時、なぜか呼び出しメソッドの ExecuteIfBound() がないので IsBound() で確認してから Execute() で呼び出しをしないとなりません。


テスト5:BlueprintImplementableEvent

BlueprintImplementableEventを使うとC++側で宣言を行い実装はBP側で、呼び出しはC++で行うことができます。

内部的にはイベントで行われています。


ATest.h

// 実装はBP側でC++では宣言のみ

UFUNCTION(Category = "Test", BlueprintImplementableEvent, BlueprintCallable)
void TestImpl(float _DeltaTime);

BP側では以下のように処理を実装できます。

ImplementableEvent.png

BPが継承関係になっている場合、親クラスの実装も呼ぶことができます。

ParentImplementableEvent.png

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


まとめ

用語が とても 紛らわしい。。

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

あと基本的にブループリントでの都合を重視しているためUnrealC++のみで考えると不要な実装も多いため混乱します。(DECLARE_DYNAMIC_DELEGATE_OneParamの宣言など)

UnrealC++だけで使用する場合は、(多分)動的デリゲートのほうが処理がかかると思いますので、通常デリゲート(静的デリゲート?)も使い分けたほうがいいと思われます。:worried: