Unreal有一套自己的反射机制,编码都是围绕UObject
在进行,其中包括接口的使用方法也是一样;我刚开始接触接口是在蓝图中的使用,
蓝图中使用接口相对来说比较简单,只要定义了蓝图接口,在任意地方都可以被调用,通过传递调用的对象来执行接口方法,
在C++中有所不同,但依然遵循蓝图通信协议;我们定义的接口当然是要在蓝图及C++都可以被调用到,
这样编码才会方便后续的拓展,有两种接口,分别是C++层和C++及蓝图都需要被调用到的接口
#1. 蓝图使用接口
蓝图的接口都是应用在蓝图层,不能被作用到C++,蓝图的功能相对来说是比较独立于C++的,
C++的功能可以被映射到蓝图,反之不可以
#1-1.定义蓝图接口
通过Blueprints
中的Blueprint Interface
来创建一个蓝图接口
在蓝图接口中添加自己需要的接口方法
添加接口需要输入参数和返回值类型,Inputs
输入参数,Outputs
返回数据
#1-2. 使用蓝图接口
在需要实现接口的蓝图类中打开Class Settings
在类设置中,添加我们定义的蓝图接口,这里也可以定义C++定义的接口,不过不能被调用到
因为C++和蓝图并不是同一作用域的,C++不知道蓝图有哪些方法,相对来说C++更底层,蓝图是应用层
在图表列表中会展示我们接口定义的方法,因为这是纯蓝图的,可以被完全兼容使用
蓝图接口的使用,需要注意需要传递Target
,也就是接口执行的对象,该对象必须已经实现了这个接口,
然后执行这个接口里面的方法,蓝图中任意地方都可以调用接口
#2. C++使用接口
C++中使用接口相对蓝图来说复杂多了,并且容易晕,其实是在遵循蓝图通信协议
#2-1. 定义C++中的接口
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UExampleInterface : public UInterface
{
GENERATED_BODY()
};
class MONTAGETEST_API IExampleInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintNativeEvent)
void BlueprintCall(); // Blueprint & C++
UFUNCTION()
virtual void CommonCall() = 0;// C++ Only
};
在我们新建一个接口的时候,会生成2个部分的代码类,其实UE中的接口是使用类在模拟的,并不是我们传统编程语言
里面的接口,也许是为了统一管理吧,效率考虑
第一部分UExampleInterface
,上面有提示This class does not need to be modified.
告诉我们不能修改这个类,这个类
是反射的基础构建,C++是强类型语言,约束接口的类型,可以通过这个类型来查找的
第二部分IExampleInterface
,以I开头表示这是一个接口(Interface),并且有提示告诉我们添加接口方法到这个类,
这个类是被继承来实现这些接口方法
UFUNCTION()
virtual void CommonCall() = 0;// C++ Only
virtual
加上方法后面=0
表示这是一个纯虚方法,纯虚方法是不需要方法实现的,在子类中必须重写实现
虚函数表
就是指这种方法的专业术语
这里面定义的是一个只能在C++层使用的接口,并且必须这样写
UFUNCTION(BlueprintNativeEvent)
void BlueprintCall(); // Blueprint & C++
这是一个普通的蓝图通信方法定义,使用的BlueprintNativeEvent
来标记,这个方法可以在C++及蓝图都可以被调用
相关资料见:#. UE中蓝图和C++相互调用
但这个方法有点特殊,他在接口中定义的时候是不需要添加后缀_Implementation
并且可以通过编译器的编译
#2-2. 实现C++定义的接口
UCLASS()
class MONTAGETEST_API AExampleInterfaceImplActor : public AActor, public IExampleInterface
{
GENERATED_BODY()
public:
virtual void CommonCall() override;
UFUNCTION(BlueprintNativeEvent)
void BlueprintCall();
virtual void BlueprintCall_Implementation() override;
};
#include "ExampleInterfaceImplActor.h"
void AExampleInterfaceImplActor::CommonCall()
{
GLog->Logf(TEXT("I am common method call from interface"));
}
void AExampleInterfaceImplActor::BlueprintCall_Implementation()
{
GLog->Logf(TEXT("I am BlueprintCall_Implementation from interface"));
}
virtual void CommonCall() override;
和通用C++语言语法一致,重写实现父类中(模拟接口)中的纯虚方法
UFUNCTION(BlueprintNativeEvent)
void BlueprintCall();
virtual void BlueprintCall_Implementation() override;
如果需要把接口应用到C++和蓝图中,需要这样写,见接口中定义的方法包括标记复制过来
然后重写接口中的BlueprintCall_Implementation()
UE反射代码生成的方法,也是和蓝图通信的方法签名一样的
// UFUNCTION(BlueprintNativeEvent)
// void BlueprintCall();
virtual void BlueprintCall_Implementation() override;
刚才测试注释上面的代码也可以正常,之前不可以估计UE抽风了,最好全部写上,标准写法
在蓝图中继承我们定义的C++类,可以看到我们把接口也继承过来了,这个时候UE会自动识别蓝图接口
需要调用父类的Parent:Blueprint Call
蓝图节点,在接口方法上右击可以找到实现,默认情况下,接口会被蓝图覆盖,
如果C++层也需要执行,需要执行一次父类调用
#2-3. C++调用接口
void AExampleInterfaceCallActor::BeginPlay()
{
Super::BeginPlay();
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsWithInterface(this, UExampleInterface::StaticClass(), AllActors);
if (AllActors[0]->GetClass()->ImplementsInterface(UExampleInterface::StaticClass()))
{
IExampleInterface* ExampleInterface = Cast<IExampleInterface>(AllActors[0]);
ExampleInterface->CommonCall();
ExampleInterface->Execute_BlueprintCall(AllActors[0]);
//IExampleInterface::Execute_BlueprintCall(AllActors[0]);
}
// ExampleInterface->BlueprintCall();
}
非常简单粗暴好用的测试代码,我把需要测试的Actor
全部都已经放到了场景中
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsWithInterface(this, UExampleInterface::StaticClass(), AllActors);
将场景中,也就是代码GetWorld()
实现了UExampleInterface
的Actor全部找出来,我的场景中只有一个蓝图Actor实现了这个接口
在编译器层面来说,这个接口是有类型的,使用UExampleInterface
这个来约束,可以通过静态方法取到UClass
类型
AllActors[0]->GetClass()->ImplementsInterface(UExampleInterface::StaticClass())
安全编码
IExampleInterface* ExampleInterface = Cast<IExampleInterface>(AllActors[0]);
因为接口是假的,使用类来模拟的,当然可以Cast
了,子类转父类,基本对象语言法则
ExampleInterface->CommonCall();
遵循C++语言的接口方法可以直接调用
如果是调用多功能接口方法,比如一个需要调用到蓝图,并且C++层也需要的话,不能直接调用,直接调用程序会崩溃
#1. 通过反射来调用(不推荐,我用的时候卡了一下,我以为UE崩溃了)
IExampleInterface::Execute_BlueprintCall(AllActors[0]);
使用反射生成的静态方法来直接调用,好处是到处都可以调用,有点类似蓝图,方便省事
#2. 通过反射生成方法来调用
ExampleInterface->Execute_BlueprintCall(AllActors[0]);
好处是效率高,编码统一,这个方法是反射生成的,加了前缀Execute_
无论使用哪一种方式,我们都需要指定一个Target,执行目标接口的方法=执行目标的方法
经验总结
有时候,我们的内容不全部C++层的,这个时候如果从C++层调用到蓝图,那么怎么桥接过去,可以使用
接口,接口有桥接C++和蓝图的功能
;
比如:动画蓝图我使用的纯蓝图制作(好像动画蓝图只能蓝图的,因为有连接端子,并且蓝图层也不支持自定义;
引擎都会有一些非常底层的功能,不允许用户自定义的,比如Unity的Transform也不能被继承重写)
这个时候我需要在C++层调用到动画蓝图,这个时候不能直接通过对象来调用接口的方法,
因为C++和蓝图并不是统一作用域,使用对象的方式来调用接口的方法,根本获取不到这个对象
UAnimInstance* TargetAnimBP = MeshComp->GetAnimInstance();
if (TargetAnimBP->GetClass()->ImplementsInterface(UCombo_Interface::StaticClass()))//OK
{
//ICombo_Interface* Interface = Cast<ICombo_Interface>(TargetAnimBP);//NULL
//Interface->SetNextComboSection(NextSectionName); //Crash
ICombo_Interface::Execute_SetNextComboSection(TargetAnimBP, NextSectionName);//Best
}
动画蓝图并不是从C++继承过去的,可以看到并没有继承接口,这个动画蓝图实现了我们C++层定义的接口,
UAnimInstance* TargetAnimBP = MeshComp->GetAnimInstance();
自定义动画蓝图可以这样获取,比较特殊的
TargetAnimBP->GetClass()->ImplementsInterface(UCombo_Interface::StaticClass())
可以通过,说明C++层的接口可以被正确的识别
ICombo_Interface* Interface = Cast<ICombo_Interface>(TargetAnimBP);//NULL
Interface->SetNextComboSection(NextSectionName); //Crash
这里不行的,因为C++和蓝图并不是同一作用域的,只有纯C++层的接口才可以使用对象来调用
ICombo_Interface::Execute_SetNextComboSection(TargetAnimBP, NextSectionName);//Best
但使用反射生成的全局静态方法可以正确执行,静态的接口方法作用全局,对象作用C++层