Choose your operating system:
Windows
macOS
Linux
选择实现方法:
Blueprints
C++
概述
接口负责定义一系列共有的行为或功能,这些行为或功能在不同Actor中可以有不同的实现方法。当你为不同Actor实现了相同类型的功能时,适合使用此通信方法。
接口通信的一个常见应用场景是,为项目中的多个Actor实现某种通用行为。这些Actor类可以是门、窗、汽车等。每个Actor类都不同,并且在调用"打开接口(Open Interface)"函数时会执行不同的行为。
在此示例中,建议你使用接口通信而非类型转换,因为你可以对所有Actor执行相同的函数调用,而不必单独去转换每个类。
此外,与类型转换相比,接口还具备性能优势,因为加载一个需要转换成其他类型的 Actor,如果不谨慎处理,可能会造成链式加载,即加载单个Actor导致更多Actor加载到内存中。
此方法要求每个Actor都实现接口,以便访问其共有功能。此外,你还需要引用Actor,以便通过引用来调用接口函数。采用这种通信方法时,当前Actor和目标Actor之间是一对一关系。
目标
在此快速指南中,你将学习如何创建一个简单的交互系统,通过在两个不同Actor间通信,学习接口的用法。
任务
-
创建一个Interact函数作为接口。
-
创建可交互的电灯和门Actor,并为它们实现接口。
-
修改ThirdPersonCharacter蓝图,使其调用附近对象上的Interact接口函数。
1 - 准备工作
-
在菜单的 新项目类别(New Project Categories) 部分中,选择 游戏(Games) ,然后点击 下一步(Next) 。
-
选择 第三人称(Third Person) 模板,然后点击 下一步(Next) 。
-
选择 蓝图(Blueprint) 和 含初学者内容包(With Starter Content) 选项,然后点击 创建项目(Create Project) 。
阶段成果
你已经创建了一个新的第三人称项目,现在可以开始实现接口了。
2 - 创建接口
-
在 内容浏览器(Content Browser) 中右键点击,然后选择 蓝图(Blueprints) > 蓝图接口(Blueprint Interface) 。将接口命名为 BPI_Interact 。
命名蓝图接口时, BPI_ 是常见的名称前缀。
-
在 内容浏览器(Content Browser) 中双击 BPI_Interact 将其打开。 在 函数(Functions) 列表下,将第一个函数命名为 Interact 。
-
编译(Compile) 并 保存(Save) 接口。
阶段成果
在此部分中,你创建了接口并添加了函数 Interact 。任何实现此接口的Actor现在都可以添加此函数。
3 - 创建交互式电灯
-
在 内容浏览器(Content Browser) 中转到 初学者内容包(Starter Content) > 蓝图(Blueprints) 。右键点击 Blueprint_CeilingLight ,然后选择 复制(Duplicate) 。将此蓝图命名为 BP_Lamp ,将其移动到游戏文件夹。
-
在 内容浏览器(Content Browser) 中双击 BPI_Lamp 将其打开。右键点击 事件图表(Event Graph) ,然后搜索并选择 添加自定义事件(Add Custom Event) 。将事件命名为 ToggleLight 。
-
从 ToggleLight 节点拖出一根引线,然后搜索并选择 Flip Flop 。
-
将 Point Light 1 组件拖动到 事件图表(Event Graph) 以创建参考节点。从 Point Light 1 节点拖出一根引线,然后搜索并选择 Set Visibility 。将 Flip Flop 节点的 A引脚(A pin) 连接到 Set Visibility 节点,如下所示。
-
复制 Point Light 1 和 Set Visibility 节点,将它们连接到 Flip Flop 节点的 B引脚(B pin) 。将 New Visibility(新可视性) 设置为True。
-
点击菜单栏中的 类设置(Class Settings) ,然后导航至 细节(Details) 面板。
-
向下滚动到 接口(Interfaces) 部分,点击 添加(Add) 下拉菜单,然后搜索并选择 BPI_Interact 。 编译(Compile) 并 保存(Save) 蓝图。
-
转到 我的蓝图(My Blueprint) 选项卡下的 接口(Interf aces) 部分。右键点击 Interact 接口函数,然后选择 实现(Implement) 事件(event) 。你将会看到 Event Interact 节点显示在 事件图表(Event Graph) 中。
-
从 Event Interact 节点拖出一根引线,然后搜索并选择 ToggleLight 。
-
编译(Compile) 并 保存(Save) 蓝图。
阶段成果
在此小节中,你复制了天花板电灯蓝图,添加了用于开关光源的自定义事件。此外,你还实现了 BPI_Interact 接口,并让 Interact 函数触发 ToggleLight 事件。
4 - 创建可交互的门
-
在 内容浏览器(Content Browser) 中点击右键,在 创建基本资产(Create Basic Asset) 分段下点击 蓝图类(Blueprint Class) 。
-
选择 Actor 类作为父类,并将蓝图命名为 BP_Door 。
-
在 内容浏览器(Content Browser) 中双击 BPI_Door 将其打开。然后在蓝图编辑器中,转到 组件(Components) 面板,点击 添加组件(Add Component) 下拉菜单。搜索并选择 静态网格体(Static Mesh) ,将组件命名为 Frame 。这会将静态网格体组件添加到蓝图。
-
添加另一个 静态网格体(Static Mesh) 组件,将其命名为 Door 。
-
选择 Frame 组件,在 细节(Details) 面板中,点击 静态网格体(Static Mesh) 下拉菜单,然后搜索并选择 SM_DoorFrame 。
-
针对 Door 组件重复执行以上步骤,并添加 SM_Door 静态网格体。
-
在选中 Door 组件时,将 Y 位置设置为 45.0 ,如下所示。你将会看到与框架对齐的门。
-
右键点击 事件图表(Event Graph) ,然后搜索并选择 添加自定义事件(Add Custom Event) 。将事件命名为 OpenDoor 。重复执行该过程,创建另一个名为 CloseDoor 的事件。
-
从 OpenDoor 事件拖出一根引线,然后搜索并选择 添加时间轴(Add Timeline) 。将时间轴命名为 TM_Door 。
-
将 CloseDoor 事件连接到 TM_Door 上的 恢复(Reverse) 执行引脚。
-
双击 TM_Door 将其打开。点击 添加浮点曲线(Add Float Curve) 按钮,添加浮点轨道并将其命名为 Alpha 。将长度设置为 1.00 。
-
右键点击图表,然后选择 将键添加到CurveFloat_1(Add key to CurveFloat_1) 以添加新点。将 时间(Time) 和 值(Value) 设置为 0.0 。
-
将 时间(Time) 和 值(Value) 设置为 1.0 ,重复执行以上步骤以添加另一个点。
-
返回到 事件图表(Event Graph) ,将 Door 静态网格体组件拖动到 事件图表(Event Graph) 中以创建节点。从 Door 节点拖出一根引线,然后搜索并选择 SetRelativeRotation 。
-
将 更新(Update) 引脚从 TM_Door 连接到 SetRelativeRotation 节点。右键点击 SetRelativeRotation 节点的 新旋转(New Rotation) 引脚,然后选择 分割结构体引脚(Split Struct Pin) 。
-
右键点击 事件图表(Event Graph) ,然后搜索并选择 Lerp浮点(Lerp Float) 。将 Lerp 节点的 返回值(Return Value) 连接到 SetRelativeRotation 节点的 偏转(Yaw) 引脚。将 TM_Door 的 Alpha 引脚连接到 Lerp 节点的 Alpha 引脚。最后,将 B 的值设置为 90.0 ,如下所示。
-
从 TM_Door 的 已完成(Finished) 引脚拖出一根引线,然后搜索并选择 Retriggerable Delay 。将节点的值设置为 2.0 。
-
从 Retriggerable Delay 节点拖出一根引线,然后搜索并选择 CloseDoor 。
-
点击菜单栏上的 类设置(Class Settings) 。
-
在 接口(Interfaces) 部分中点击 添加(Add) 下拉菜单,然后搜索并选择 BPI_Interact 。
-
转到 我的蓝图(My Blueprint) 选项卡下的 接口(Interf aces) 部分。右键点击 Interact 接口函数,然后选择 实现(Implement) 事件(event) 。你将会看到 Event Interact 节点显示在 事件图表(Event Graph) 中。
-
从 Event Interact 节点拖出一根引线,然后搜索并选择 OpenDoor 。
-
编译(Compile) 并 保存(Save) 蓝图。
阶段成果
在此小节中,你创建了一扇可交互的门Actor。当 BPI_Interact 接口的 Interact 函数被调用时,门会打开。
5 - 修改玩家蓝图
-
在关卡中选择 ThirdPersonCharacter 蓝图。转到 世界大纲视图(World Outliner) ,点击 编辑ThirdPersonCharacter(Edit ThirdPersonCharacter) 以打开蓝图编辑器。
-
在蓝图编辑器中,转到 组件(Components) 面板,点击 添加组件(Add Component) 按钮。搜索并选择 球体碰撞(Sphere Collision) 。这会将球体碰撞组件添加到蓝图。
-
选择 球体碰撞(Sphere Collision) 组件之后,转到 细节(Details) 面板,将 半径(Radius) 设置为200。
-
右键点击 球体碰撞(Sphere Collision) 组件,然后选择事件 OnComponentBeginOverlap ,将其添加到 事件图表(Event Graph) 。
-
从 On Component Begin Overlap 节点拖出一根引线,然后搜索并选择 Interact (Message) 。确保选择位于 BPI交互(BPI Interact) 类别下的函数。
-
将 Other Actor 节点从 On Component Begin Overlap 事件连接到 交互(Interact) 函数上的 目标(Target) 引脚。
-
编译(Compile) 并 保存(Save) 蓝图。
阶段成果
在此小节中,你在 ThirdPersonCharacter 蓝图中添加了 球体(sphere) 碰撞组件,以检测重叠到的Actor。当Actor与球体重叠时,蓝图将从该Actor上的 BPI_Interact 接口调用 交互(Interact) 函数。如果Actor未实现接口,节点的执行将失败。
6 - 测试交互系统
-
将 BP_Door 和 BP_Lamp Actor拖动到关卡。
-
点击 运行(Play) ,然后接近每个Actor以查看它们与玩家的交互。
阶段成果
在此小节中,你测试了门和电灯Actor,确认了交互系统可以按照预期工作。
在此快速入门指南中,你学习了如何让多个Actor具备相同的接口函数,并以不同方式实现它们的接口。此外,你还了解了为何当多个Actor具备类似功能时,更适合用接口而非类型转换。
后续步骤
现在你已了解如何使用接口,接下来可以查看 Actor通信 文档中的其他通信类型。
概述
接口负责定义一系列共有的行为或功能,这些行为或功能在不同Actor中可以有不同的实现方法。当你为不同Actor实现了相同类型的功能时,适合使用此通信方法。
例如,当你需要为多个蓝图(例如门、窗、汽车等)实现一个共有的打开(Open)行为,你可以选择接口。在此示例中,每个Actor都是不同的类,并且在调用"打开"时会做出不同响应。
此外,与类型转换相比,接口还具备性能优势,因为加载一个需要转换成其他类型的 Actor,如果不谨慎处理,可能会造成链式加载,即加载单个Actor导致更多Actor加载到内存中。
此方法要求每个Actor都实现接口,以便访问其共有功能(即该接口)。
目标
在此快速指南中,你将学习如何创建一个简单的交互系统,通过在两个不同Actor间通信,学习接口的用法。
任务
-
创建一个Interact函数作为接口。
-
创建可交互的电灯和门Actor,并为它们实现接口。
-
修改BpCommunication角色类,使其调用附近对象上的Interact接口函数。
1 - 准备工作
-
在菜单的 新项目类别(New Project Categories) 部分中,选择 游戏(Games) ,然后点击 下一步(Next) 。
-
选择 第三人称(Third Person) 模板,然后点击 下一步(Next) 。
-
选择启用了 含初学者内容包(With Starter Content) 选项的 C++ 项目,然后点击 创建项目(Create Project) 。
阶段成果
你已经创建了一个新的第三人称项目,现在可以开始实现接口了。
2 - 创建接口
-
从 C++类向导 中新建名为 InteractInterface 的新虚幻接口类。
-
在IInteractInterface.h的类默认值中,声明以下方法。
public:
UFUNCTION() virtual void OnInteract() = 0;
-
编译你的代码。
已完成代码
InteractionInterface.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "InteractInterface.generated.h"
// 此类不需要修改。
UINTERFACE(MinimalAPI)
class UInteractInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class BPCOMMUNICATION_API IInteractInterface
{
GENERATED_BODY()
// 将接口函数添加到此类。这是将会被继承以实现此接口的类。
public:
UFUNCTION()
virtual void OnInteract() = 0;
};
阶段成果
在此部分中,你创建了一个接口并添加了函数 OnInteract 。所有实现此接口的Actor蓝图现在都可以使用此函数。
2 - 创建可交互的天花板光源Actor
1.从 C++类向导 中新建名为 CeilingLight 的新Actor类。
![图像替换文本](image_4.png)(convert:false)
在 **CeilingLight.h** 的类默认值中,声明以下类库。
#include "InteractInterface.h"
然后,实现以下代码。
UCLASS()
class BPCOMMUNICATION_API ACeilingLight : public AActor, public IInteractInterface
{
GENERATED_BODY()
public:
virtual void OnInteract();
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UPointLightComponent* PointLightComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UStaticMeshComponent* StaticMeshComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Brightness;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SourceRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor Color;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bIsLightOn;
UFUNCTION()
void ToggleLight();
}
-
然后导航至 CeilingLight.cpp 并添加以下头文件。
#include "Components/PointLightComponent.h"
然后实现以下代码。
ACeilingLight::ACeilingLight() { // 将此Actor设置为每帧调用Tick()。 如果你不需要此特性,你可以关闭它以提升性能。 PrimaryActorTick.bCanEverTick = true; RootComponent = CreateDefaultSubobject<URootComponent>(TEXT("RootComponent")); PointLightComp = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComp")); StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp")); PointLightComp->AttachToComponent(RootComponent,FAttachmentTransformRules::KeepRelativeTransform); StaticMeshComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); PointLightComp->SetWorldLocation(FVector(0, 0, -130)); Brightness = 1700.f; Color = FLinearColor(1.f, 0.77f, 0.46f); SourceRadius = 3.5f; PointLightComp->SetIntensity(Brightness); PointLightComp->SetLightColor(Color); PointLightComp->SetSourceRadius(SourceRadius); } void ACeilingLight::OnInteract() { ToggleLight(); } void ACeilingLight::ToggleLight() { if (bbIsLightOn) { PointLightComp->SetVisibility(false); bbIsLightOn = false; } else { PointLightComp->SetVisibility(true); bbIsLightOn = true; } }
-
编译你的代码。
-
在 C++类文件夹(C++ Classes folder) 中,右键点击 CeilingLight Actor,然后在 C++类操作(C++ Class Actions) 下拉菜单中,选择 基于CeilingLight创建蓝图类(Create Blueprint class based on CeilingLight) 。将蓝图命名为 Bp_CeilingLight 。
-
在 BP_CeilingLight 类默认值中,导航至 组件(Components) 面板,然后选择 StaticMeshComp 。
-
在 细节(Details) 面板中,导航至 静态网格体(Static Mesh) 类别,选择 静态网格体(Static Mesh) 变量旁边的下拉箭头,然后搜索并选择 SM_Lamp_Ceiling 。
已完成代码
CeilingLight.h
//在项目设置(Project Settings)的描述(Description)页面中填写版权声明。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractInterface.h"
#include "CeilingLight.generated.h"
UCLASS()
class BPCOMMUNICATION_API ACeilingLight : public AActor, public IInteractInterface
{
GENERATED_BODY()
public:
// 为此Actor的属性设置默认值
ACeilingLight();
virtual void OnInteract();
protected:
// 当游戏开始或重生(Spawn)时被调用
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UPointLightComponent* PointLightComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UStaticMeshComponent* StaticMeshComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Brightness;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SourceRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor Color;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bbIsLightOn;
public:
// 每一帧都调用
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void ToggleLight();
};
CeilingLight.cpp
//版权所有Epic Games, Inc。保留所有权利。
#include "CeilingLight.h"
#include "Components/PointLightComponent.h"
// 设置默认值
ACeilingLight::ACeilingLight()
{
// 将此Actor设置为每帧调用更新函数()。 如果你不需要此特性,你可以关闭它以提升性能。
PrimaryActorTick.bCanEverTick = true;
RootComponent = CreateDefaultSubobject<URootComponent>(TEXT("RootComponent"));
PointLightComp = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLightComp"));
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));
PointLightComp->AttachToComponent(RootComponent,FAttachmentTransformRules::KeepRelativeTransform);
StaticMeshComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
PointLightComp->SetWorldLocation(FVector(0, 0, -130));
Brightness = 1700.f;
Color = FLinearColor(1.f, 0.77f, 0.46f);
SourceRadius = 3.5f;
PointLightComp->SetIntensity(Brightness);
PointLightComp->SetLightColor(Color);
PointLightComp->SetSourceRadius(SourceRadius);
}
void ACeilingLight::OnInteract()
{
ToggleLight();
}
// 当游戏开始或重生(Spawn)时被调用
void ACeilingLight::BeginPlay()
{
Super::BeginPlay();
}
void ACeilingLight::ToggleLight()
{
if (bIsLightOn)
{
PointLightComp->SetVisibility(false);
bIsLightOn = false;
}
else
{
PointLightComp->SetVisibility(true);
bIsLightOn = true;
}
}
// 每一帧都调用
void ACeilingLight::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
阶段成果
在此小节中,你创建了天花板光源Actor类,添加了用于切换光源可见性的自定义函数。此外,你实现了 Interact(交互) 接口,以便触发 ToggleLight 事件的执行。
4 - 创建可交互的门
-
在 C++类向导(C++ Class Wizard) 中新建名为 DoorActor 的 Actor 类。
-
导航至DoorActor.h文件,并声明以下include:
#include "Components/TimelineComponent.h" #include "InteractInterface.h"
-
在DoorActor类命名空间中,你需要从交互接口继承。
UCLASS() class BPCOMMUNICATION_API ADoorActor : public AActor, public IInteractInterface
-
然后声明以下类定义。
// 用于保留曲线资产的变量 UPROPERTY(EditAnywhere) UCurveFloat* DoorTimelineFloatCurve; UFUNCTION() virtual void OnInteract(); protected: // 当游戏开始或重生(Spawn)时被调用 virtual void BeginPlay() override; //用于表示门资产的网格体组件 UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UStaticMeshComponent* DoorFrame; UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UStaticMeshComponent* Door; //用于对门网格体进行动画处理的时间轴组件 UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UTimelineComponent* DoorTimelineComp; //用于处理我们的更新轨道事件的浮点轨道签名 FOnTimelineFloat UpdateFunctionFloat; //用于使用时间轴图表更新门相对位置的函数 UFUNCTION() void UpdateTimelineComp(float Output);
-
在 DoorActor.cpp 中,实现以下类定义
ADoorActor::ADoorActor() { //创建我们的默认组件 DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh")); Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh")); DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp")); //设置我们的附件 DoorFrame->SetupAttachment(RootComponent); Door->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform); Door->SetRelativeLocation(FVector(0, 35, 0)); } void ADoorActor::OnInteract() { DoorTimelineComp->Play(); } // 当游戏开始或重生(Spawn)时被调用 void ADoorActor::BeginPlay() { Super::BeginPlay(); //将浮点轨道绑定到UpdateTimelineComp函数的输出 UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp); //如果有浮点曲线,将其图表绑定到我们的更新函数 if (DoorTimelineFloatCurve) { DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat); } } void ADoorActor::UpdateTimelineComp(float Output) { // 基于时间轴曲线的输出创建并设置门的新相对位置 FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f); Door->SetRelativeRotation(DoorNewRotation); }
-
编译你的代码。
-
从 内容浏览器 中选择 添加/导入(Add/Import) > 杂项(Miscellaneous) > 曲线(Curve) 。
-
选择 CurveFloat 并将CurveFloat资产命名为 DoorCurveFloat
-
双击DoorCurveFloat资产。向你的浮点曲线添加两个键,为一个键赋予时间值(0,0),另一个键赋予时间值(4,90)。
-
按住Shift键并选中这两个键,将它们设置为自动立方体插值,然后保存曲线。
-
保存你的DoorCurveFloat。
-
在内容浏览器中,导航至 C++类文件夹(C++ Classes folder) ,右键点击DoorActor类。选择 基于门Actor创建蓝图类(Create Blueprint Class based on Door Actor) ,并将你的蓝图Actor命名为 Bp_DoorActor 。
1.在 BP_DoorActor 的 类默认值(class defaults) 中,找到 组件(Components) 选项卡然后选择 DoorFrame 静态网格体(Static Mesh) 组件,导航至 细节(Details) 面板,将静态网格体更改为 SM_DoorFrame 。
![图像替换文本](image_12.png)(convert:false)
-
接下来,在组件(Components)选项卡中选择DoorMesh组件。导航至细节面板,将静态网格体更改为 SM_Door 。
-
在细节面板中,从门时间轴浮点曲线(Door Timeline Float Curve)下拉菜单中选择DoorCurveFloat。
-
编译并保存蓝图。
已完成代码
DoorActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TimelineComponent.h"
#include "InteractInterface.h"
#include "DoorActor.generated.h"
UCLASS()
class BPCOMMUNICATION_API ADoorActor : public AActor, public IInteractInterface
{
GENERATED_BODY()
public:
// 为此Actor的属性设置默认值
ADoorActor();
// 用于保留曲线资产的变量
UPROPERTY(EditAnywhere)
UCurveFloat* DoorTimelineFloatCurve;
UFUNCTION()
virtual void OnInteract();
protected:
// 当游戏开始或重生(Spawn)时被调用
virtual void BeginPlay() override;
//用于表示门资产的网格体组件
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
UStaticMeshComponent* DoorFrame;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
UStaticMeshComponent* Door;
//用于对门网格体进行动画处理的时间轴组件
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
UTimelineComponent* DoorTimelineComp;
//用于处理我们的更新轨道事件的浮点轨道签名
FOnTimelineFloat UpdateFunctionFloat;
//用于使用时间轴图表更新门相对位置的函数
UFUNCTION()
void UpdateTimelineComp(float Output);
public:
// 每一帧都调用
virtual void Tick(float DeltaTime) override;
};
DoorActor.cpp
#include "DoorActor.h"
// 设置默认值
ADoorActor::ADoorActor()
{
// 将此Actor设置为每帧调用更新函数()。 如果你不需要此特性,你可以关闭它以提升性能。
PrimaryActorTick.bCanEverTick = true;
//创建我们的默认组件
DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh"));
Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));
//设置绑定
DoorFrame->SetupAttachment(RootComponent);
Door->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform);
Door->SetRelativeLocation(FVector(0, 35, 0));
}
void ADoorActor::OnInteract()
{
DoorTimelineComp->Play();
}
// 在游戏开始或重生(Spawn)时被调用
void ADoorActor::BeginPlay()
{
Super::BeginPlay();
//将浮点轨道绑定到UpdateTimelineComp函数的输出
UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp);
//如果有浮点曲线,将其图表绑定到我们的更新函数
if (DoorTimelineFloatCurve)
{
DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat);
}
}
void ADoorActor::UpdateTimelineComp(float Output)
{
// 基于时间轴曲线的输出创建并设置门的新相对位置
FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f);
Door->SetRelativeRotation(DoorNewRotation);
}
// 每一帧都调用
void ADoorActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
阶段成果
在此部分中,你创建了一个可交互的门Actor,它将在 Interact 接口的 OnInteract 方法被调用时打开。
5 - 修改BPCommunicationCharacter类
-
打开BpCommunicationCharacter.h文件,然后在其类定义中声明以下内容。
protected: virtual void NotifyActorBeginOverlap(AActor* OtherActor); class USphereComponent* SphereComp;
-
导航至 BpCommunicationCharacter .cpp文件并声明以下类库。
#include "Components/SphereComponent.h" #include "InteractInterface.h"
然后实现以下类方法。
ABPCommunicationCharacter::ABPCommunicationCharacter() { SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp")); SphereComp->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform); SphereComp->SetSphereRadius(200); } void ABPCommunicationCharacter::NotifyActorBeginOverlap(AActor* OtherActor) { if (IInteractInterface* ActorCheck = Cast<IInteractInterface>(OtherActor)) { ActorCheck->OnInteract(); } }
-
编译你的代码。
已完成代码
BpCommunicationCharacter.h
//版权所有Epic Games, Inc。保留所有权利。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BPCommunicationCharacter.generated.h"
UCLASS(config=Game)
class ABPCommunicationCharacter : public ACharacter
{
GENERATED_BODY()
/** 用于将摄像机放置在角色后面的摄像机升降臂 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* CameraBoom;
/** 跟随摄像机 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* FollowCamera;
public:
ABPCommunicationCharacter();
/** 基本旋转速度,以"度/秒"为单位。其他计量方式可能会影响最终旋转速度。*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseTurnRate;
/** 基本仰视/俯视速度,以"度/秒"为单位。其他计量方式可能会影响最终速度。*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
float BaseLookUpRate;
protected:
virtual void NotifyActorBeginOverlap(AActor* OtherActor);
class USphereComponent* SphereComp;
/** 重置VR中的HMD方向。*/
void OnResetVR();
/** 出现向前/向后输入时调用 */
void MoveForward(float Value);
/** 出现侧边到侧边输入时调用 */
void MoveRight(float Value);
/**
* 通过输入进行调用,以给定的速度旋转。
* @param速度 这是标准化速度,即1.0表示100%的所需旋转速度
*/
void TurnAtRate(float Rate);
/**
* 通过输入进行调用,以给定的速度仰视/俯视旋转。
* @param速度 这是标准化速度,即1.0表示100%的所需旋转速度
*/
void LookUpAtRate(float Rate);
/** 当触摸输入开始时的处理程序。*/
void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
/** 当触摸输入停止时的处理程序。*/
void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
protected:
// APawn接口
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// APawn接口结束
public:
/** 返回CameraBoom子对象 **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** 返回FollowCamera子object **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
BpCommunicationCharacter.cpp
//版权所有Epic Games, Inc。保留所有权利。
#include "BPCommunicationCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "Components/SphereComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "InteractInterface.h"
#include "GameFramework/SpringArmComponent.h"
//////////////////////////////////////////////////////////////////////////
// ABPCommunicationCharacter
ABPCommunicationCharacter::ABPCommunicationCharacter()
{
// 设置碰撞胶囊体的大小
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// 设置我们的输入旋转速度
BaseTurnRate = 45.f;
BaseLookUpRate = 45.f;
// 不在控制器旋转时旋转。使其仅影响摄像机。
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 配置角色移动
GetCharacterMovement()->bOrientRotationToMovement = true; // 角色沿输入方向移动...
GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...以此旋转速度
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
// 创建摄像机升降臂(如果发生碰撞,朝着玩家推进)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f; // 摄像机在角色后面的这个距离上跟随
CameraBoom->bUsePawnControlRotation = true; // 基于控制器旋转升降臂
// 创建跟随摄像机
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 将摄像机附于升降臂末端,调整升降臂,使其与控制器方向一致
FollowCamera->bUsePawnControlRotation = false; // 摄像机不相对于升降臂旋转
SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
SphereComp->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform);
SphereComp->SetSphereRadius(200);
// 注意:网格体组件上的骨骼网格体和动画蓝图引用(从角色继承)
// 是在名为MyCharacter的派生蓝图资产中设置的(以避免C++中的直接内容引用)
}
//////////////////////////////////////////////////////////////////////////
// 输入
void ABPCommunicationCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// 设置游戏的按键绑定
check(PlayerInputComponent);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
PlayerInputComponent->BindAxis("MoveForward", this, &ABPCommunicationCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ABPCommunicationCharacter::MoveRight);
// 我们提供了两个版本的旋转绑定来分别处理不同类型的设备
// "turn"处理提供绝对增量的设备,例如鼠标。
// "turnrate"适用于我们选择以变化速度方式进行处理的设备,例如模拟摇杆
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("TurnRate", this, &ABPCommunicationCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis("LookUpRate", this, &ABPCommunicationCharacter::LookUpAtRate);
// 处理触摸设备
PlayerInputComponent->BindTouch(IE_Pressed, this, &ABPCommunicationCharacter::TouchStarted);
PlayerInputComponent->BindTouch(IE_Released, this, &ABPCommunicationCharacter::TouchStopped);
// VR头戴设备功能
PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &ABPCommunicationCharacter::OnResetVR);
}
void ABPCommunicationCharacter::NotifyActorBeginOverlap(AActor* OtherActor)
{
if (IInteractInterface* ActorCheck = Cast<IInteractInterface>(OtherActor))
{
ActorCheck->OnInteract();
}
}
void ABPCommunicationCharacter::OnResetVR()
{
// 如果在虚幻编辑器中通过"添加功能(Add Feature)"将BPCommunication添加到项目,则BPCommunication.Build.cs中HeadMountedDisplay上的依赖关系不会自动传播,
// 并将产生连接器错误。
// 你需要:
// 将"HeadMountedDisplay"添加到[YourProject].Build.cs PublicDependencyModuleNames,以便成功构建(如果支持VR则适用)。
// 或者:
// 注释掉或删除下面对ResetOrientationAndPosition的调用(如果不支持VR则适用)
UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}
void ABPCommunicationCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
Jump();
}
void ABPCommunicationCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
StopJumping();
}
void ABPCommunicationCharacter::TurnAtRate(float Rate)
{
// 根据速度信息计算此帧的增量
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}
void ABPCommunicationCharacter::LookUpAtRate(float Rate)
{
// 根据速度信息计算此帧的增量
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}
void ABPCommunicationCharacter::MoveForward(float Value)
{
if ((Controller != nullptr) && (Value != 0.0f))
{
// 找出向前方向
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 获取向前向量
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void ABPCommunicationCharacter::MoveRight(float Value)
{
if ( (Controller != nullptr) && (Value != 0.0f) )
{
// 找出向右方向
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// 获取向右向量
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// 添加该方向上的动作
AddMovementInput(Direction, Value);
}
}
阶段成果
在此部分中,你在 ThirdPersonCharacter 类中添加了 球体(sphere) 组件,以便进行碰撞检测。当Actor与球体重叠时,角色会调用交互接口,并触发碰撞对象的 OnInteract 函数。
6 - 测试交互系统
-
将 BP_DoorActor 和 BP_CeilingLamp 蓝图的实例拖动到关卡视口中。
-
点击运行(Play),然后接近每个蓝图以查看它们与玩家的交互。
阶段成果
在此小节中,你测试了交互式门和天花板电灯Actor蓝图,确认了交互系统可以按照预期工作。
在此快速入门指南中,你学习了如何让多个Actor具备相同的接口函数,并以不同方式实现它们的接口。此外,你还了解了为何当多个Actor具备类似功能时,更适合用接口而非类型转换。
后续步骤
现在你已了解如何使用接口,接下来可以查看 Actor通信 文档中的其他通信类型。