UEC++ 代理/委託

代理:

  • 代理可以幫助我們解決一對一或是一對多的任務分配工作。主要可以幫助我們解決通知問題。我們可以通過代理完成調用某一個對象的一個函數,而不直接持有該對象的任何指針。
  • 代理就是為你跑腿送信的,你可以不用關心給送信的目標人具體是誰,只要按照約定好的信件格式進行送信即可
  • 更簡單理解,想去調用某個函數,但並不是直接去調用,而是通過另一個入口去調用(代理)

分類:

  • 單播代理 只能進行通知一個人
  • 多播代理 可以進行多人通知
  • 動態代理 可以被序列化(這體現在於藍圖進行交互,C++中可以將通知事件進行藍圖廣播)

單播代理:

通過宏進行構建,單播代理只能綁定一個通知對象,無法進行多個對象通知、

單播代理分為有返回值與無返回值兩種

代理可使用聲明宏

函數簽名

聲明宏

void Function()

DECLARE_DELEGATE(DelegateName)

void Function(Param1)

DECLARE_DELEGATE_OneParam(DelegateName, Param1Type)

void Function(Param1, Param2)

DECLARE_DELEGATE_TwoParams(DelegateName, Param1Type, Param2Type)

void Function(Param1, Param2, ...)

DECLARE_DELEGATE_<Num>Params(DelegateName, Param1Type, Param2Type, ...)

<RetValType> Function()

DECLARE_DELEGATE_RetVal(RetValType, DelegateName)

<RetValType> Function(Param1)

DECLARE_DELEGATE_RetVal_OneParam(RetValType, DelegateName, Param1Type)

<RetValType> Function(Param1, Param2)

DECLARE_DELEGATE_RetVal_TwoParams(RetValType, DelegateName, Param1Type, Param2Type)

<RetValType> Function(Param1, Param2, ...)

DECLARE_DELEGATE_RetVal_<Num>Params(RetValType, DelegateName, Param1Type, Param2Type, ...)

 

常用綁定函數:

  • BindUObject 綁定UObject類型對象成員函數的代理
  • BindSP 綁定基於共享引用的成員函數代理
  • BindRaw 綁定原始自定義對象成員函數的代理,操作調用需要注意執行需要檢查
  • IsBound BindStatic 綁定全局函數成為代理
  • UnBind 解除綁定代理關係

注意:綁定中傳遞的對象類型必須和函數指針所屬類的類型相同否則綁定會報錯

調用執行:

  • 為了保證調用的安全性,執行Execute函數之前需要檢查是否存在有效綁定使用函數、
  • IsBound Execute 調用代理通知,不安全,需要注意
  • ExecuteIfBound 調用代理通知,安全,但是有返回類型的回調函數無法使用此函數執行回調
  • IsBound 檢查當前是否存在有效代理綁定

構建步驟:

  • 通過宏進行聲明代理對象類型(根據回調函數選擇不同的宏)
  • 使用代理類型進行構建代理對象
  • 綁定回調對象,和操作函數
  • 執行代理對象回調
// Actor1.h
// 頭文件下
DECLARE_DELEGATE(DelegateOne)
DECLARE_DELEGATE_RetVal_OneParam(int32 ,DelegateTwo, int32)
// 變數聲明
    class AActor2* ac2;

    DelegateOne DegOne;
    DelegateTwo DegTwo;
// Actor1.cpp 
// 這裡將程式碼寫在了BeginPlay中,方便測試
    ac2 = GetWorld()->SpawnActor<AActor2>(AActor2::StaticClass());

    // 綁定無參無返回值單播代理
    DegOne.BindUObject(ac2, &AActor2::CallBackNone);
    DegOne.ExecuteIfBound();
    // 綁定有參有返回值單播代理
    DegTwo.BindUObject(ac2, &AActor2::CallBackRes);
    int32 num = 0;
    num = DegTwo.Execute(100);
    UKismetSystemLibrary::PrintString(this, FString::Printf(TEXT("%d"),num));

///////////////////////////////////////////////////////

// Actor2.h
//聲明兩個被用來綁定的的函數
    void CallBackNone();
    int32 CallBackRes(int32 num);
// Actor2.cpp
void AActor2::CallBackNone()
{
    UKismetSystemLibrary::PrintString(this, TEXT("無返回值無參數函數調用!"));
}

int32 AActor2::CallBackRes(int32 num)
{
    UKismetSystemLibrary::PrintString(this, TEXT("有返回值有參數函數調用!"));
    return num;
}

測試結果:

 

 

 多播代理:

無法構建具有返回值的多播代理——多播代理無返回值

DECLARE_MULTICAST_DELEGATE[_Const, _RetVal, _etc.] (DelegateName)

 

多播代理綁定函數 

函數

說明

“Add()”

將函數委託添加到該多播委託的調用列表中。

“AddStatic()”

添加原始C++指針全局函數委託。

“AddRaw()”

添加原始C++指針委託。原始指針不使用任何類型的引用,因此如果從委託下面刪除了對象,則調用此函數可能不安全。調用Execute()時請小心!

“AddSP()”

添加基於共享指針的(快速、非執行緒安全)成員函數委託。共享指針委託保留對對象的弱引用。

“AddUObject()”

添加基於UObject的成員函數委託。UObject委託保留對對象的弱引用。

“Remove()”

從該多播委託的調用列表中刪除函數(性能為O(N))。請注意,委託的順序可能不會被保留!

“RemoveAll()”

從該多播委託的調用列表中刪除綁定到指定UserObject的所有函數。請注意,委託的順序可能不會被保留!

廣博: 調用函數Broadcast,但是調用不保證執行順序的正確性

構建步驟:

  • 使用宏構建代理類型
  • 使用代理類型構建多播代理對象
  • 添加綁定代理
  • 執行調用

多播代理執行使用的是 Broadcast() 進行執行函數

動態代理:

  • 允許被序列化的數據結構,這將使得代理可以被數據化提供給藍圖進行使用,達到在CPP中調用代理廣播,事件通知到藍圖中。
  • 動態代理和普通代理基本相同,分為單向和多向,動態代理無法使用帶有返回值的函數進行構建(動態單播除外,並且單播無法在藍圖中綁定無法使用宏BlueprintAssignable修飾)
  • UE中的大部分通知事件均使用動態代理(方便藍圖操作),如碰撞通知

動態單播代理:

  • DECLARE_DYNAMIC_DELEGATE[_Const, _RetVal, etc.]( DelegateName )

動態多播代理:

  • DECLARE_DYNAMIC_MULTICAST_DELEGATE[_Const, _RetVal, etc.]( DelegateName )

操作函數:

  • BindDynamic( UserObject, FuncName ) 在動態代理上調用BindDynamic()的輔助宏。
  • AddDynamic( UserObject, FuncName ) 在動態多播代理上調用AddDynamic()的輔助宏。
  • RemoveDynamic( UserObject, FuncName ) 在動態多播代理上調用RemoveDynamic()的輔助宏。

 

與單播多播區別:

  • 動態代理構建類型名稱需要用 F 開頭(動態代理實現機制構建了類)
  • 動態代理對象類型可以使用UPROPERTY標記,其他代理均無法使用(不加編譯可過,調用出錯)
  • 動態代理綁定對象的函數需要使用UFUNCTION進行描述(因為需要跟隨代理被序列化)

構建:

// Actor1.h
DECLARE_DYNAMIC_DELEGATE(FDelegateTree); // 注意分號
// 變數定義
    class AActor2* ac2;
    FDelegateTree DegTree;
// Actor1.cpp
    ac2 = GetWorld()->SpawnActor<AActor2>(AActor2::StaticClass());

    DegTree.BindDynamic(ac2, &AActor2::CallBackNone);
    if (DegTree.IsBound()) {
        DegTree.ExecuteIfBound();
    }

//////////////////////////////////////////////////////////
// Actor2.h
    UFUNCTION()
    void CallBackNone();
// Actor2.cpp
void AActor2::CallBackNone()
{
    UKismetSystemLibrary::PrintString(this, TEXT("無返回值無參數函數調用!"));
}

測試結果:

 動態代理用於藍圖:

在構建動態代理提供藍圖使用時,需要在代理上增加標記宏UPROPERTY(BlueprintAssignable)