UEC++ 介面
詞義廣泛,用來陳述功能,選項,與其他程式結構進行溝通的方式。介面抽象出了交互結構,提供了兩個未知邏輯交互的便捷性。對於編程中,如何更好的設計低耦合程式起到了至關重要的作用。設計者可以在互不關心的情況下,進行友好的程式設計,並且通過介面來完成設計的整合交互。
虛幻引擎中,加入了介面設計,從一定程度上「去掉了」多繼承。介面可以幫助我們解決在不同類型的類之間卻有相同行為的特性。介面的設計增加了程式碼編寫的便捷性。
例如在設計射擊類遊戲時,我們需要子彈與場景中的物體進行交互,場景中的桌椅板凳,角色,怪物(都是獨立的對象)都希望受到子彈的攻擊傷害。那麼子彈在打到目標後要逐一排查,審查目標是否屬於上述的對象!這很麻煩!但是我們可以通過介面,增加上述目標具有受傷的能力。當子彈打到目標時,我只需要檢查目標是否繼承受傷的介面,如果有,則調用介面函數即可!
構建介面類:
我們可以直接在虛幻編輯器中繼承介面類,然後完成構建
編寫介面:
- 如果在C++中希望獲得介面能力,則需要繼承介面。需要注意的是,必須繼承I開頭的介面名稱,並且繼承修飾為public。不要嘗試重寫介面中的函數!
- 如果介面中的函數使用BlueprintNativeEvent說明,則在繼承類中可以編寫同名函數,並用後綴「_Implementation」進行標記。
- 如果介面中的函數使用BlueprintImplementableEvent說明,則無法在C++的繼承類中實現介面函數
實現介面:
繼承I類介面完畢後,可以選擇的將介面中的函數進行定義。如果需要定義,則需要將介面中函數說明是BlueprintNativeEvent的函數進行定義。
注意,不要省略override,函數的返回值,參數列表需要和介面的一致
調用操作:
調用函數,持有繼承介面對象指針,第一步先轉換到I類指針,調用Execute_介面函數名,參數第一位需要傳遞原對象指針,後面直接按照原函數參數填入即可
整體程式碼演示:
// TestInterface.h UINTERFACE(MinimalAPI) class UTestInterface : public UInterface { GENERATED_BODY() }; /** * 1、U類中不能去寫介面函數,只能用來檢查是否繼承了介面類 * 2、介面函數,必須寫在I類中,並且必須寫共有域中 * 3、介面函數在介面類中不能進行定義 * */ class MX_API ITestInterface { GENERATED_BODY() // Add interface functions to this class. This is the class that will be inherited to implement this interface. public: // 定義介面函數 UFUNCTION(BlueprintNativeEvent) void Notify_None(); UFUNCTION(BlueprintNativeEvent) int32 Notify_RetVal(); UFUNCTION(BlueprintNativeEvent) int32 Notify_RetVal_Params(int32 Num); }; ///////////////////////////////////////////////////////////// // Actor2.h public: // Called every frame virtual void Tick(float DeltaTime) override; // 實現介面 virtual void Notify_None_Implementation() override; virtual int32 Notify_RetVal_Implementation() override; virtual int32 Notify_RetVal_Params_Implementation(int32 Num) override; // Actor2.cpp void AActor2::Notify_None_Implementation() { UKismetSystemLibrary::PrintString(this, TEXT("----無參無返回值----")); } int32 AActor2::Notify_RetVal_Implementation() { UKismetSystemLibrary::PrintString(this, TEXT("----無參有返回值----")); return 0; } int32 AActor2::Notify_RetVal_Params_Implementation(int32 Num) { UKismetSystemLibrary::PrintString(this, TEXT("----有參有返回值----")); return Num; }void AActor2::Notify_None_Implementation() { UKismetSystemLibrary::PrintString(this, TEXT("----無參無返回值----")); } int32 AActor2::Notify_RetVal_Implementation() { UKismetSystemLibrary::PrintString(this, TEXT("----無參有返回值----")); return 0; } int32 AActor2::Notify_RetVal_Params_Implementation(int32 Num) { UKismetSystemLibrary::PrintString(this, TEXT("----有參有返回值----")); return Num; } ///////////////////////////////////////////////////////// //Actor1.cpp ac2 = GetWorld()->SpawnActor<AActor2>(AActor2::StaticClass()); // 檢查是否繼承了介面 ITestInterface* testInterface = Cast<ITestInterface>(ac2); // 如果繼承了介面,就執行介面函數 if (testInterface) { testInterface->Execute_Notify_None(ac2); testInterface->Execute_Notify_RetVal(ac2); testInterface->Execute_Notify_RetVal_Params(ac2,10); }
測試結果:

包裹介面:
藉助模板類TScriptInterface可以將介面包裹,用於使用UPROPERTY描述,並且可以暴露到藍圖中。使用時同普通介面一樣使用。介面不支援智慧指針的管理,所以需要使用TS類進行管理
UPROPERTY(EditAnywhere)
TScriptInterface<ITestInterface> TestInterface;
藍圖繼承介面:
如果介面在藍圖中被繼承,則需要注意下面的問題
- 如果函數沒有返回類型,則在藍圖中當作事件Event使用
- 如果函數存在返回類型或是存在傳遞引用參數,則在藍圖中當作函數使用
- 介面函數說明符使用BlueprintNativeEvent或是BlueprintImplementableEvent標記都可以在藍圖中找到
總結:
- 介面函數需要定在I開頭的類中,不要修改訪問域public關鍵字,聲明需要使用宏標記BlueprintNativeEvent或BlueprintImplementableEvent
- 如需繼承介面,繼承I類,繼承關係public
- 介面中的函數禁止重寫
- 在繼承類中實現介面函數,並添加後綴_Implementation,需要注意,函數前加入虛函數關鍵字virtual,函數結尾加override關鍵字(可以不添加,但是建議加上,加強函數編寫正確性檢查),在CPP文件中實現邏輯
- 調用函數,持有繼承介面對象指針,第一步先轉換到I類指針,調用Execute_介面函數名,參數第一位需要傳遞原對象指針,後面直接按照原函數參數填入即可
- 檢查某一個類是否實現了對應介面可以使用如下語法進行檢查
- obj->GetClass()->ImplementsInterface(U類型::StaticClass());
- act->GetClass()->ImplementsInterface(UMyInterface::StaticClass());
- act是對象指指針
介面的優缺點:
優點:
- 具備多態特性,介面衍生類支援里氏轉換原則
- 介面可以使得整個繼承系統更加的乾淨單一
- 介面可以規範類的具體行為
- 介面可以隔離開發中的開發耦合,我們只需要針對介面去編碼,無需關心具體行為
- 介面繼承可以使得繼承關係中出現真正的操作父類
缺點:
- 丟失了C++中的廣泛繼承特性
- 介面拘束了類型的屬性拓展,無法進行更詳細的內容定義
- 繼承關係中容易讓人混淆,介面本身不具備真正的繼承特性

