UE C++ のデリゲート宣言マクロの書き方をフローチャートにして整理した。

宣言方法の判断フロー

デリゲート宣言の判断フロー

  • Q1: Blueprintへ公開するか?
    • No → C++ 側だけで完結する通常のデリゲート
    • Yes → DYNAMIC 系(BPからバインド/呼び出しできる代わりに遅い)
  • Q2: 複数の関数を登録できるようにするか?
    • No → 単一デリゲート。戻り値ありなら末尾に _RetVal を付ける
    • Yes → MULTICAST 系。戻り値は持てない(複数呼ばれた場合どれを返すか決まらないため)
  • 引数がある場合は末尾に _OneParam / _TwoParams ... を付ける

マクロの構造

上の4種はすべて DECLARE_[DYNAMIC_][MULTICAST_]DELEGATE[_RetVal][_XParams] という1つのテンプレートの組み合わせで、各パーツが判断フローの質問にそのまま対応している。

マクロの構造分解

  • DYNAMIC … Q1(BPへ公開するか)
  • MULTICAST … Q2(複数登録するか)
  • RetVal … 戻り値の有無(Multicastには付けられない)
  • XParams … 引数の数(OneParam / TwoParams ...)

例:

// BP呼び出し & 複数登録 & 引数二つ
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnScoreChangedSignature,
    int32, NewScore, class APlayerState*, OwningPlayer);

// C++呼び出し & 単数登録 & 戻り値あり & 引数1つ
DECLARE_DELEGATE_RetVal_OneParam(bool, FOnDogSucceededWoofing,
    class ADog* /* Dog */);

非DynamicとDynamicで引数の書き方が少し違う点に注意。非Dynamicは型だけ(変数名はコメント扱い)、Dynamicは型と変数名をカンマ区切りで両方書く必要がある。

// 非Dynamic: 型のみ(変数名はコメントで補足するだけ)
DECLARE_DELEGATE_OneParam(FOnScoreChangedSignature, int32 /* NewScore */);

// Dynamic: 型と変数名をペアで書く
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnScoreChangedSignature, int32, NewScore);

宣言した型はメンバ変数としてはどれも同じ形で持てる。BPからバインドさせたい場合は UPROPERTY(BlueprintAssignable) を付ける(Dynamic系のみ有効)。

UCLASS()
class ABUIPlayerState : public APlayerState
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintAssignable)
    FOnScoreChangedSignature OnScoreChangedDelegate;
};

バインド方法

非Dynamic 単一Bind* 系(対象関数に UFUNCTION() は不要):

OnScoreChangedDelegate.BindUObject(this, &ThisClass::OnScoreChanged); // UObject
OnScoreChangedDelegate.BindRaw(SomeSlateThing, &SSlomeSlateThing::OnScoreChangedRaw); // 非UObject
OnScoreChangedDelegate.BindLambda([](int32 NewScore) { /* ... */ });
OnScoreChangedDelegate.BindStatic(&StaticFunction);
OnScoreChangedDelegate.BindWeakLambda(WeakObject, [WeakObject](int32 NewScore) { /* ... */ }); // 破棄済みなら発火しない
OnScoreChangedDelegate.BindSP(SharedPtrObject, &Class::Method);

非Dynamic MulticastAdd* 系(複数登録できる):

OnScoreChangedDelegate.AddUObject(this, &ThisClass::OnScoreChanged);
OnScoreChangedDelegate.AddLambda([](int32 NewScore) { /* ... */ });

AddUObject/BindUObjectは対象への弱参照を保持するため、対象が破棄された後に呼び出されてもクラッシュしないのでUObject継承している場合はこちら推奨。登録自体を自動で解除してくれるわけではないので不要になったら解除する。

Dynamic 単一BindDynamic。対象関数は UFUNCTION() 必須:

OnScoreChangedDelegate.BindDynamic(this, &ThisClass::OnScoreChanged);

Dynamic MulticastAddDynamic / AddUniqueDynamicAddUniqueDynamic は同じ関数の重複登録を防げる:

OnScoreChangedDelegate.AddDynamic(this, &ThisClass::OnScoreChanged);
OnScoreChangedDelegate.AddUniqueDynamic(this, &ThisClass::OnScoreChanged);

解除方法

  • 非Dynamic 単一: .Unbind()
  • 非Dynamic Multicast: .RemoveAll(this)AddUObject/AddRaw/AddSPで登録したthis宛の分をまとめて外す) / .Remove(Handle)Add*の戻り値FDelegateHandleを保持しておいて個別に外す)
  • Dynamic 単一・Multicast共通: RemoveDynamic(this, &ThisClass::OnScoreChanged)(バインドと同じ引数で指定)。Multicastは.RemoveAll(this)でも一括で外せる
OnScoreChangedDelegate.Unbind();                 // 非Dynamic単一
OnScoreChangedDelegate.RemoveAll(this);           // 非Dynamic Multicast / Dynamic Multicast 共通
OnScoreChangedDelegate.RemoveDynamic(this, &ThisClass::OnScoreChanged); // Dynamic

実行

  • 単一デリゲート: .Execute()(未バインドだとクラッシュ) / .ExecuteIfBound()(安全)
  • Multicastデリゲート: .Broadcast(引数...)

補足

  • 同じシグネチャのデリゲートを複数持ちたいだけなら、型自体を使い回してよい(OnDogJumpedDelegate / OnDogWoofedDelegate を同じ FOnDogEventSignature で宣言、など)
  • Dynamicで変数名まで必要になるのは、BP公開のためだと思われるのであとで調べる。

参考