05 - 接口UINTERFACE

本文主要说明了UE5中有关接口UINTERFACE()的基础概念。

UINTERFACE

接口UINTERFACE()确保继承它的类实现它定义的的函数,这在某个功能可能被大量复杂且差异显著的类共享的情况下非常有用。

声明接口

和普通的虚幻类UCLASS()不同,虚幻接口:

  • 使用UINTERFACE()而不是UCLASS()宏;
  • 继承自UInterface类而不是UObject类。

实际上,UINTERFACE类不是实际的接口,而是为反射系统提供可见性的空白类。

通过类生成向导生成的虚幻接口如下:

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ReactToTriggerInterface.generated.h"
 
/*
此类无需修改。
用于反射系统可见性的空白类。
使用UINTERFACE宏。
继承自UInterface。
*/
UINTERFACE(MinimalAPI, Blueprintable)
class UReactToTriggerInterface : public UInterface
{
	GENERATED_BODY()
};
 
/* 实际接口声明。 */
class IReactToTriggerInterface
{
	GENERATED_BODY()
 
	// 将接口函数添加到此类。此类将被继承以实现此接口。
public:
	// 在此处添加接口函数声明
};

声明并实现接口函数

总体来说,需要注意的点如下:

  1. 在声明接口函数时,所有函数都应该在I开头的接口类中声明,并且它们的访问权限是公开的。
  2. 在C++类中实现接口函数时,需要:
    • 包含接口头文件;
    • 继承I前缀的接口类;

例如:

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ReactToTriggerInterface.h"
#include "Trap.generated.h"
 
UCLASS(Blueprintable, Category="MyGame")
class ATrap : public AActor, public IReactToTriggerInterface
{
	GENERATED_BODY()
 
public:
	// 在此添加接口函数重载
};

接下来具体看看接口函数是怎么声明的。

仅C++调用的函数

要让声明的函数仅供C++调用,该函数可以不带UFUNCTION()宏,但必须是虚函数:

/* 实际接口声明。 */
class IReactToTriggerInterface
{
	GENERATED_BODY()
 
public:
	virtual bool ReactToTrigger();
};

然后在接口类的.cpp文件提供默认实现,或者在派生类中重写该函数即可。需要注意的是,用这种方法声明的C++接口函数对蓝图不可见,不能在Blueprintable接口中使用。

蓝图可调用的函数

要创建蓝图可调用的接口函数,需要:

  • 在带 BlueprintCallable 说明符的函数声明中指定一个 UFUNCTION() 宏。
  • 使用 BlueprintImplementableEventBlueprintNativeEvent 说明符。

此外,如果想在C++中调用这类函数,必须使用特殊的静态Execute_函数封装器:

// OriginalObject is an object that implements the IReactToTriggerInterface
bool bReacted = IReactToTriggerInterface::Execute_ReactToTrigger(OriginalObject);

该封装器用于在C++和蓝图中定义的实现间实现通信,可调用包括C++和蓝图重载在内的所有函数实现(一般是蓝图重载优先)。

BlueprintImplementableEvent

该说明符声明的接口函数不能在C++中重载,但可以在任何实现或继承该接口的蓝图类中重载。

在C++接口类中声明这类函数的例子如下:

#pragma once

#include "ReactToTriggerInterface.generated.h"

/*
用于反射系统可见性的空白类。
使用UINTERFACE宏。
继承自UInterface。
*/
UINTERFACE(MinimalAPI, Blueprintable)
class UReactToTriggerInterface : public UInterface
{
	GENERATED_BODY()
};

/* 实际的接口声明。 */
class IReactToTriggerInterface
{
	GENERATED_BODY()

public:
	/* 只能在蓝图中实现的React To Trigger函数版本。 */
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category=Trigger Reaction)
	bool ReactToTrigger();
};

BlueprintNativeEvent

该说明符声明的接口函数可以在C++或蓝图中实现。声明函数的方式和上一个说明符类似,这里看看C++中是如何实现的,需要在实现接口的类中定义_Implementation后缀的函数:

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ReactToTriggerInterface.h"
#include "Trap.generated.h"

UCLASS(Blueprintable, Category="MyGame")
class ATrap : public AActor, public IReactToTriggerInterface
{
	GENERATED_BODY()

public:
	virtual bool ReactToTrigger() override;
	
	// 蓝图原生事件重载
	bool ReactToTrigger_Implementation() override;
};

类型转换

虚幻引擎的转换系统支持从一个接口转换到另一个接口,或者在适当的情况下,从一个接口转换到一个虚幻类型:

/* 如果接口被实现,则ReactingObject不为空 */
IReactToTriggerInterface* ReactingObject = Cast<IReactToTriggerInterface>(OriginalObject);

/* 如果ReactingObject不为空,且其实现了ISomeOtherInterface,则DifferentInterface不为空 */
ISomeOtherInterface* DifferentInterface = Cast<ISomeOtherInterface>(ReactingObject);

/* 如果ReactingObject不为空,且OriginalObject是一个AActor或AActor派生的类,则ReactingActor不为空 */
AActor* ReactingActor = Cast<AActor>(ReactingObject);

存储接口引用

可以通过使用TScriptInterface来存储对象实现特定接口的引用:

UMyObject* MyObjectPtr;
TScriptInterface<IMyInterface> MyScriptInterface;

if (MyObjectPtr->Implements<UMyInterface>())
{
	MyScriptInterface = TScriptInterface<IMyInterface>(MyObjectPtr);
}

// MyScriptInterface holds a reference to MyObjectPtr and MyInterfacePtr

从该引用中可以通过GetObject()获取指向对象的指针,通过GetInterface()获取指向源对象实现的接口指针:

UMyObject* MyRetrievedObjectPtr = MyScriptInterface.GetObject();

IMyInterface* MyRetrievedInterfacePtr = MyScriptInterface.GetInterface();

参考资料

  • UE5 虚幻引擎UEC++从基础到进阶_哔哩哔哩_bilibili

  • 虚幻引擎中的接口 | 虚幻引擎 5.6 文档 | Epic Developer Community