【UE4 C++ 基础知识】<8> Delegate 委托
阅读原文时间:2023年07月11日阅读:1

概念

  • UE4中的delegate(委托)常用于解耦不同对象之间的关联:委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系。

    监听者通过将响应函数绑定到委托上,使得委托触发时立即收到通知,并进行相关逻辑处理。

  • 委托,又称代理,本质是一个特殊类的对象,它内部可以储存(一个或多个)函数指针、调用参数和返回值。

  • 委托签名声明可存在于全局范围内、命名空间内、甚至类声明内。此类声明可能不在于函数体内

  • 可以是返回一个值的函数。

  • 最多4个"载荷"变量。

  • 最多8个函数参数。

    //定义一个无参普通单播委托
    DECLARE_DELEGATE( DelegateName )

    //定义一个无参普通单播委托, 带返回参数
    DECLARE_DELEGATE_RetVal( ReturnValueType, DelegateName )

    //定义一个无参动态单播委托, 带返回参数
    DECLARE_DYNAMIC_DELEGATE_RetVal

    //定义一个无参普通多播委托
    DECLARE_MULTICAST_DELEGATE( DelegateName )

    //定义一个无参事件(特殊的多播委托)
    DECLARE_EVENT( OwningType, EventName )

    //定义一个无参动态单播委托
    DECLARE_DYNAMIC_DELEGATE( DelegateName)

    //定义一个无参动态多播委托
    DECLARE_DYNAMIC_MULTICAST_DELEGATE( DelegateName )

单播委托

  • 绑定单个可调用对象

  • 支持返回值.

  • 支持参数

  • 不支持反射以及序列化

    //不支持返回,支持参数
    DECLARE_DELEGATE_*
    DECLARE_DELEGATE(DelegateName)
    DECLARE_DELEGATE_OneParam(DelegateName, Param1Type)
    DECLARE_DELEGATE_Params(DelegateName, Param1Type, Param2Type, …)

    //支持返回RetValType,支持参数
    DECLARE_DELEGATE_RetVal_*
    DECLARE_DELEGATE_RetVal(RetValType, DelegateName)
    DECLARE_DELEGATE_RetVal_OneParam(RetValType, DelegateName, Param1Type)
    DECLARE_DELEGATE_RetVal_Params(RetValType, DelegateName, Param1Type, Param2Type, …)

  • Bind 绑定到现有委托对象。

  • BindStatic 绑定原始C++指针全局函数委托。

  • BindRaw 绑定原始C++指针委托。由于原始指针不使用任何类型的引用,因此在删除目标对象后调用Execute&nbsp;或&nbsp;ExecuteIfBound 会不安全。

  • BindLambda

  • BindSP 绑定基于指针的共享成员函数委托。共享指针委托会保留对对象的弱引用。可使用 ExecuteIfBound() 进行调用。

    TSharedRef<FLogWriter> LogWriter(new FLogWriter());
    WriteToLogDelegate.BindSP(LogWriter, &FLogWriter::WriteToLog);
  • BindUObject 绑定 UObject 的成员函数委托。UObject 委托会保留对你的对象 UObject 的弱引用。可使用 ExecuteIfBound() 进行调用。

  • BindUFunction绑定由UFUNCTION标记的函数

  • UnBind 取消绑定此委托。

  • Execute 不检查其绑定情况即执行一个委托

  • ExecuteIfBound 检查一个委托是否已绑定,如是,则调用Execute

  • IsBound 检查一个委托是否已绑定,经常出现在包含 Execute 调用的代码前

不带参数Delegate

//InventoryGameMode.h类外声明
DECLARE_DELEGATE(FStandardDelegateSignature)

//InventoryGameMode.h类成员声明变量
FStandardDelegateSignature MyStandardDelegate;

//DelegateListener.cpp绑定委托
MyInventoryGM->MyStandardDelegate.BindUObject(this, &ADelegateListener::EnableLight);
//DelegateListener.cpp解绑委托
MyInventoryGM->MyStandardDelegate.Unbind();

//MyTriggerVolume.cpp调用委托,间接调用函数
MyInventoryGM->MyStandardDelegate.ExecuteIfBound();

带参数Delegate

//InventoryGameMode.h类外声明
DECLARE_DELEGATE_OneParam(FParamDelegateSignature,FLinearColor)

//InventoryGameMode.h类成员声明变量
FParamDelegateSignature MyParamDelegate;

//ParamDelegateListener.cpp 绑定委托
MyInventoryGM->MyParamDelegate.BindUObject(this, &AParamDelegateListener::SetLightColor);
//ParamDelegateListener.cpp 解绑委托
MyInventoryGM->MyParamDelegate.Unbind();

//MyTriggerVolume.cpp调用委托,间接调用函数
auto Color = FLinearColor(1, 0, 0, 1);
MyInventoryGM->MyParamDelegate.ExecuteIfBound(Color);


多播委托

  • 可以绑定多个回调函数,当其触发时,所有绑定的回调函数都会执行, 实质是维持了一个单播委托的数组

  • 没有返回值.

  • 支持参数

  • 不支持反射以及序列化

    DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)
    DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastDelegateSignature, FString)

  • Add 将函数委托添加到该多播委托的调用列表中。

  • AddStatic 添加原始C++指针全局函数委托。

  • AddRaw 添加原始C++指针委托。原始指针不使用任何类型的引用,因此如果从委托下面删除了对象,则调用此函数可能不安全。调用Execute()时请小心!

  • AddSP 添加基于共享指针的(快速、非线程安全)成员函数委托。共享指针委托保留对对象的弱引用。

  • AddUObject 添加基于UObject的成员函数委托。UObject委托保留对对象的弱引用。

  • Remove从该多播委托的调用列表中删除函数(性能为O(N))。请注意,委托的顺序可能不会被保留!

  • RemoveAll 从该多播委托的调用列表中删除绑定到指定UserObject的所有函数。请注意,委托的顺序可能不会被保留!

  • Broadcast 将该委托广播给所有绑定的对象,但可能已过期的对象除外。

    //InventoryGameMode.h类外声明
    DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)

    //InventoryGameMode.h类成员声明变量
    FMulticastDelegateSignature MyMulticastDelegate;

    //MulticastDelegateListener.h声明
    FDelegateHandle MyDelegateHandle;
    //MulticastDelegateListener.cpp 绑定委托
    MyDelegateHandle = MyInventoryGM->MyMulticastDelegate.AddUObject(this, &AMulticastDelegateListener::ToggleLight);
    //MulticastDelegateListener.cpp 解绑委托
    MyInventoryGM->MyMulticastDelegate.Remove(MyDelegateHandle);

    //MyTriggerVolume.cpp调用委托,间接调用函数
    MyInventoryGM->MyMulticastDelegate.Broadcast();

    //绑定lambda
    FDelegateHandle Handle;
    Handle=MDOneParam.AddLambda(
    [](FString str)
    {
    UE_LOG(LogTemp,Warning,TEXT("Lambda Call,Param Value:%s"),*str);
    }
    )


动态委托

  • 支持反射以及序列化,但其执行速度比常规委托慢。可

  • 支持返回值

  • 不支持参数

  • 动态多播委托可以暴露给蓝图,在蓝图中动态绑定相关的函数,而普通的委托和动态多播委托则不行

  • 参数构成:(委托名,参数类型1,参数名1,参数类型2,参数名2)

动态多播代理的名称开头须为F,否则会编译或调用报错

DECLARE_DYNAMIC_DELEGATE_*
DECLARE_DYNAMIC_DELEGATE(FOnGameWindowCloseButtonClickedDelegate);  // 无参、无返回值
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAssetLoaded, class UObject*, Loaded); // 1个参数、无返回值

DECLARE_DYNAMIC_DELEGATE_RetVal_*
DECLARE_DYNAMIC_DELEGATE_RetVal(EMouseCursor::Type, FGetMouseCursor); // 无参、EMouseCursor::Type返回值
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(UWidget*, FGenerateWidgetForObject, UObject*, Item); // 1个参数、UWidget*返回值
DECLARE_DYNAMIC_DELEGATE_RetVal_TwoParams(FEventReply, FOnPointerEvent, FGeometry, MyGeometry, const FPointerEvent&, MouseEvent);  // 2个参数、FEventReply返回值

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDelegate)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDelegate, FString, InPrar)

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FNotifyPawnChange, float, PawnHpPercent,float,PawnPhysicalShieldPercent,float, PawnMageShieldPercent);
  • BindDynamic( UserObject, FuncName )

  • AddDynamic( UserObject, FuncName )

  • RemoveDynamic( UserObject, FuncName ) 解绑单个

  • Clear全部解绑

动态多播也支持使用Remove和Removeall,用法与多播一样

  • Execute 不检查其绑定情况即执行一个动态委托

  • ExecuteIfBound 检查一个动态委托是否已绑定,如是,则调用Execute

  • IsBound 检查一个动态委托是否已绑定,经常出现在包含 Execute 调用的代码前

  • Broadcast 将该动态多播委托广播给所有绑定的对象,但可能已过期的对象除外。

    //动态委托
    DECLARE_DYNAMIC_DELEGATE(FWDE_Dy_Sl_Zero);

    //委托变量作为参数
    UFUNCTION(BlueprintCallable, Category = "FrameWork")
    void RegFunDel(FWDE_Dy_Sl_Zero TargetFun);

  • AddDynamic绑定的方法得被UFUNCTION标记,否则绑定无效

  • 动态代理对象类型可以使用UPROPERTY标记,并设置为BlueprintAssignable,从而暴露给蓝图使用,其他代理均无法使用(不加编译可过,调用出错)

    DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FNotifyPawnChange, float, PawnHpPercent,float,PawnPhysicalShieldPercent,float, PawnMageShieldPercent);

    UPROPERTY(BlueprintAssignable)
    FNotifyPawnChange NotifyPawnChange;

    NotifyPawnChange.Broadcast(PawnHpPercent, PawnPhysicalShieldPercent, PawnMageShieldPercent);


事件

事件与 组播委托 十分相似。虽然任意类均可绑定事件,但只有声明事件的类可以调用事件 的 Broadcast、IsBound 和 Clear 函数。这意味着事件对象可在公共接口中公开,而无需让外部类访问这些敏感度函数。事件使用情况有:在纯抽象类中包含回调、限制外部类调用 Broadcast、IsBound 和 Clear 函数。

DECLARE_EVENT( OwningType, EventName ) 创建一个事件。
DECLARE_EVENT_OneParam( OwningType, EventName, Param1Type ) 创建带一个参数的事件。
DECLARE_EVENT_TwoParams( OwningType, EventName, Param1Type, Param2Type ) 创建带两个参数的事件。
DECLARE_EVENT_<Num>Params( OwningType, EventName, Param1Type, Param2Type, ...) 创建带 N 个参数的事件。

与多播委托方式相同

事件允许附带多个函数委托,然后调用事件的 Broadcast() 函数将它们一次性全部执行。

  • Broadcast() 将此事件广播到所有绑定对象,已失效的对象除外。

普通用法

//MyTriggerVolume.h 类外声明
DECLARE_EVENT(AMyTriggerVolume,FPlayerEntered)
//MyTriggerVolume.h 类内声明
FPlayerEntered OnPlayerEntered;

//MyTriggerVolume.cpp 调用委托
OnPlayerEntered.Broadcast();

//TriggerVolEventListener.cpp 绑定委托
TriggerEventSource->OnPlayerEntered.AddUObject(this, &ATriggerVolEventListener::OnTriggerEvent);

通常用法

将Event定义于类内,通常将Event对象设为私有,类外通过公开的访问接口进行绑定,触发,解绑,这种方式起到了保护隐私的作用

  • 定义

    class ADelegateActor:public AActor
    {
    GENERATED_BODY()
    public:
    /*
    *OwingType:拥有此Event的类,本例中使用本类:ADelegateActor
    *EventName:事件名称
    *ParamType:参数列表
    */
    DECLARE_EVENT_OneParam(ADelegateActor, MyDelegateEvent, FString);

    //公开对Event对象的访问接口

    MyDelegateEvent& OnEventTrigger(){return DelegateEvent;}

    private:
    //将Event设为私有,防止类外直接访问到,起到安全作用
    MyDelegateEvent DelegateEvent;
    }

  • 绑定回调

    class CallbackTarget
    {
    public:

    void FunctionForAddUObject(FString str);

    UFUNCTION()
    void FunctionForAddUFunction(FString str);
    //静态成员函数
    static StaticCallback(FString str)
    {
    UE_LOG(LogTemp,Warning,TEXT("StaticCallback Call,Param Value:%s"),str); } } //全局静态函数(静态非成员函数) void StaticFunc(FString str) { UE_LOG(LogTemp,Warning,TEXT("StaticFunc Call,Param Value:%s"),str);
    }

    CallbackTarget* Target=new CallbackTarget();
    /多播绑定回调函数以Add开头/

    //delegatehandle
    FDelegateHandle Handle;

    //AddUObject 绑定多播
    Handle=OnEventTrigger().AddUObject(Target,&CallbackTarget::FunctionForAddUObject);

    //AddUFunction 绑定多播
    Handle=OnEventTrigger().AddUFunction(Target,FName(TEXT("FunctionForAddUFunction")));

    //AddStatic 绑定全局静态函数
    Handle=OnEventTrigger().AddStatic(StaticFunc);

    //AddStatic 绑定静态成员函数
    Handle=OnEventTrigger().AddStatic(&CallbackTarget::StaticCallback);

    //AddLambda 绑定Lambda表达式
    Handle=OnEventTrigger().AddLambda(
    [](FString str)
    {
    UE_LOG(LogTemp,Warning,TEXT("Lambda Call,Param Value:%s"),*str);
    }
    )

  • 触发和解绑

    OnEventTrigger().Broadcast("DELEGATE EVENT Call");
    OnEventTrigger().Clear();

基础类实现:

/** Register/Unregister a callback for when assets are added to the registry */
DECLARE_EVENT_OneParam( IAssetRegistry, FAssetAddedEvent, const FAssetData&);
virtual FAseetAddedEvent& OnAssetAdded() = 0;

派生类实现:

DECLARE_DERIVED_EVENT( FAssetRegistry, IAssetRegistry::FAssetAddedEvent, FAssetAddedEvent);
virtual FassetAddedEvent& OnAssetAdded() override { return AssetAddedEvent; }

在派生类中声明一个派生事件时,不要在 DECLARE_DERIVED_EVENT 宏中重复函数签名。此外,DECLARE_DERIVED_EVENT 宏的最后一个参数是事件的新命名,通常与基础类型相同。


参考

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章