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++中的接口
UINTERFACE(MinimalAPI)
class UExampleInterface : public UInterface
{
GENERATED_BODY()
};
class MONTAGETEST_API IExampleInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent)
void BlueprintCall();
UFUNCTION()
virtual void CommonCall() = 0;
};
在我们新建一个接口的时候,会生成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反射代码生成的方法,也是和蓝图通信的方法签名一样的
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]);
}
}
非常简单粗暴好用的测试代码,我把需要测试的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()))
{
ICombo_Interface::Execute_SetNextComboSection(TargetAnimBP, NextSectionName);
}
动画蓝图并不是从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++层
|