2019年07月10日

UE4のデリゲートについて

こんにちは、プログラマのカコです。

今回はUE4のデリゲートについて紹介したいと思います。
と言っても初歩的な事ばかりなので
デリゲートをあんまり使ったことが無いという方に向けた内容となっております。
また、詳しい使い方はUE4の公式ドキュメントを見れば済むので
ここでは簡単な使い方や個人的な使用感を適当にかいつまんでお話します。
https://api.unrealengine.com/JPN/Programming/UnrealArchitecture/Delegates/index.html


・どんな場面で使うか。

実に多岐に渡り利用することができます。

例えば、プレイヤーの攻撃が当たった時を考えます。
まず攻撃が当たったとき用のデリゲートを用意します。

攻撃が当たったときに画面上にヒット表示を出したいとなれば、
ヒットくんをこのデリゲートに登録します。

190710@.jpg

これで攻撃が当たった時にプレイヤーは何も知らなくても
当たった!と発信するだけで勝手に画面上にヒット表示がでます。

190710A.jpg

与えたダメージを集計したい場合があるかもしれません。
その時もスコアくんをプレイヤーの同じデリゲートに登録します。

190710B.jpg

こうすることでプレイヤーは当たった!と発信するだけで
ヒットくんが画面上にヒット表示を出し、スコアくんが集計をしてくれます。

190710C.jpg

このように必要になった時に都度登録することで、
その時に誘発して発生する処理を簡単に呼び出すことが可能になります。
またこの時プレイヤーは発信しているだけなので勝手にデリゲートに登録したヒットくんやスコアくんの存在を知らずともそれぞれの処理を呼び出しています。

他にも攻撃を当てられたとき用デリゲートや倒されたとき用デリゲートなどが考えられます。
この時にこうしたいああしたいといった対応が必要になりそうな物はデリゲートにしておくと開発が楽になるでしょう。




・UE4のデリゲート
C#のデリゲートに似ている気がします。
宣言できる物が複数ありますがざっくりと以下のような感じです。

@ デリゲート
登録できる関数:1個
A マルチキャストデリゲート
登録できる関数:複数
B イベント
Aの発信できるクラス制限版
C 動的(マルチキャスト)デリゲート
@とAのブループリント公開版

大概はAのマルチキャストデリゲートを利用します。

ブループリント上に公開されるときは「イベントディスパッチャー」と表示されるので、
それに乗っ取り弊社では「On~EventDispatcher」という命名規則で宣言しています。
-----------------------------
AMyCharacter
// 攻撃が当たった時のイベントディスパッチャー宣言用マクロ
DECLARE_MULTICAST_DELEGATE(FOnHitAttackEventDispatcher);

// 攻撃が当たった時のイベントディスパッチャー
FOnHitAttackEventDispatcher OnHitEventDispatcher;
-----------------------------


この作成したキャラクターのイベントディスパッチャーにヒット表示用widgetの関数を登録します。
-----------------------------
UHitWidget
// 攻撃が当たったらヒット表示
void OnHitAttack();

// 登録
MyCharacter->OnHitAttackEventDispatcher.
AddUObject(this,&UHitWidget::OnHitAttack);
-----------------------------

あとはキャラクターが攻撃を当てたタイミングで、

OnHitEventDispatcher.Broadcast();

を呼ぶことでAMyCharacterはUHitWidgetの存在を知らずとも
UHitWidgetのヒット表示関数を呼び出せるようになります。

この作成したキャラクターのイベントディスパッチャーにヒット表示用widgetの関数を登録します。
-----------------------------
UHitWidget
// 攻撃が当たったらヒット表示
void OnHitAttack();

// 登録
MyCharacter->OnHitAttackEventDispatcher.
AddUObject(this,&UHitWidget::OnHitAttack);
-----------------------------

あとはキャラクターが攻撃を当てたタイミングで、

OnHitEventDispatcher.Broadcast();

を呼ぶことでAMyCharacterはUHitWidgetの存在を知らずとも
UHitWidgetのヒット表示関数を呼び出せるようになります。




・引数の追加
実行するときに引数を渡すことができます。

宣言用マクロの末尾に「_OneParam」や「_TwoParams」と追加することで任意の引数を渡すことが可能です。
先程の攻撃が当たった時のイベントディスパッチャーにどれくらいのダメージ量だったかを追加してみます。
-----------------------------
AMyCharacter
// 攻撃が当たった時のイベントディスパッチャー宣言用マクロ
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHitAttackEventDispatcher, float);
// 実行
OnHitEventDispatcher.Broadcast(DamageValue);
-----------------------------
UHitWidget
// 攻撃が当たったらヒット表示
void OnHitAttack(float DamageValue);
-----------------------------

これでダメージの値を渡すことができるようになりました。
「_NineParams」まであるようなので他にも何か伝えたい情報が増えてもあと8個追加することができます。

当然このイベントディスパッチャーに登録した関数の引数も増やす必要があります。
もし数十種類のオブジェクトが関数を登録している場合、その数だけ引数を修正することになるでしょう。

こうなる前に引数が増えそうで登録数の多くなりそうだと予想されるイベントディスパッチャーの引数はクラスや構造体にしてまとめておくのがオススメです。
そうすればメンバ変数を増やすだけで済みます。
「_FourParams」辺りになってきたら実装を見直すべきかもしれません。




・その他小技
当然エンジン側の実装でもデリゲートが出てきます。
例えば以下は、アセットの非同期読み込みに利用されるデリゲートです。

DECLARE_DELEGATE(FStreamableDelegate);
FStreamableManager::RequestAsyncLoad(…,FStreamableDelegate DelegateToCall, …);

読み込みリクエストの関数の引数として登場し、読み込みが完了した時に任意の関数を呼んでもらうことができるようになります。
ただし引数無しデリゲートなので特に情報を渡すことができません。

例えば、読み込んだアセットを固有の整数型で管理していた場合、読み込み完了したことが分かっても何番のアセットが終わったかまでは分かりません。

エンジンのソースを変更して引数を追加してもいいですがそれも手間です。
そんなときは、登録時にCreateUObjectを呼ぶことで任意の引数を渡すことができるようになります。

-----------------------------
RequestAsyncLoad(…,FStreamableDelegate::CreateUObject(this, &FMyClass::OnComplete, AssetId),…);

// 読み込み完了
FMyClass::OnComplete(int32 AssetId);
-----------------------------

こうすればエンジンのコードに手を加えることなく独自の実装をすることが可能です。
覚えておけば役に立つ日が来るかもしれません。



・終わりに
以上かなり説明を省きましたがUE4のデリゲート紹介でした。
呼び出す側がオブジェクトの型を知らずとも任意の関数を呼び出せるということは、余計な参照を増やさずに済むのでデリゲートを使いこなすのはかなりオススメです。

UE4はデリゲート以外にもまだまだ便利な機能はあります。
公式ドキュメントや個人ブログなどUE4は情報が充実していますので
読み漁ってみては如何でしょうか。

それでは、また。
posted by byking at 17:30| 開発