UEC++ 代理/委託

2022-09-22 18:05:13

代理:

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

分類:

  • 單播代理 只能進行通知一個人
  • 多播代理 可以進行多人通知
  • 動態代理 可以被序列化(這體現在於藍圖進行互動,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)