c++接口
这里通过一个打开宝箱的机制举例,比如当按下e的时候宝箱就会打开之类的机制
首先创建一个c++的接口类GameplayInterface,继承自Unreal Interface
接口的目的是共享API而不需要特别去指定需要实现的是什么
比如说想要打开宝箱的交互和碰到死亡角色去掠夺他们身上财物的交互,两者是不同的,不能共享之间的交互。但是我们可以把接口添加到这两个地方让他们自己实现该接口和实现不同的功能。
在创建的这个类的头文件中里面出现了两个类,但是注释告诉我们不要去修改第一个类。这个第一个类更像是一个格式上的东西,u前缀和i前缀也可以更好地区分哪一个是接口类
//GameplayInterface.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "GameplayInterface.generated.h"
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UGameplayInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class ACTIONROUGELIKE_API IGameplayInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
//我们希望调用交互函数的时候,是谁在使用它,所以调用的时候可以传入一个参数
//同时暴露给蓝图
//BlueprintNativeEvent可以让函数既可以在蓝图中实现也可以在c++中实现
//但是BlueprintImplementableEvent只能在蓝图中实现
UFUNCTION(BlueprintNativeEvent)
void Interact(APawn* InstigatorPawn);
};
然后创建ItemChest类(继承自Actor)
//ItemChest.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameplayInterface.h"
#include "GameFramework/Actor.h"
#include "ItemChest.generated.h"
UCLASS()
class ACTIONROUGELIKE_API AItemChest : public AActor, public IGameplayInterface
{
GENERATED_BODY()
//这个是实现接口的一个语法(名称_Implementation),但是这个并不是接口的一部分
//可以这么使用是因为前面的UFUNCTION(BlueprintNativeEvent)
void Interact_Implementation(APawn* InstigatorPawn) override;
public:
// Sets default values for this actor's properties
AItemChest();
protected:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* BaseMesh;
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* LidMesh;
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemChest.h"
void AItemChest::Interact_Implementation(APawn* InstigatorPawn)
{
//设置了物体的相对旋转,相对于所连接的物体
LidMesh->SetRelativeRotation(FRotator(110, 0, 0));
}
// Sets default values
AItemChest::AItemChest()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>("BaseMesh");
RootComponent = BaseMesh;
LidMesh = CreateDefaultSubobject<UStaticMeshComponent>("LidMesh");
LidMesh->SetupAttachment(BaseMesh);
}
// Called when the game starts or when spawned
void AItemChest::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AItemChest::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
碰撞查询
箱子的接口实现已经写好了,但是我们需要创建角色和箱子的交互功能。
我们打算使用角色组件去检查周围有什么物体可以进行交互
这里用到角色组件,将代码写在组件中是为了程序的解耦性。
创建c++类InteractionComponent(继承自父类ActorComponent)
//InteractionComponent.h
public:
//将在这个函数中做碰撞查询
void PrimaryInteract();
//InteractionComponent.cpp
void UInteractionComponent::PrimaryInteract()
{
FHitResult Hit;
FCollisionObjectQueryParams ObjectQueryParams;
//添加碰撞查询的类型
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic);
AActor* MyOwner = GetOwner();
FVector Start,End;
FVector EyeLocation;
FRotator EyeRotation;
//获取角色的眼睛作为出发点
//在控制器控制角色的时候将会把摄像头看向的地方作为方向
MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
Start = EyeLocation;
End = Start + 200.0f * EyeRotation.Vector();
//从关卡中的某一个点到另一个点查询两个点之间是否有可供碰撞的物体,这里是通过对象类型寻找
//第一个参数是命中的结果,第二和第三个参数分别是开始的点和结束的点
//第四个参数是碰撞查询对象的参数
GetWorld()->LineTraceSingleByObjectType(Hit, Start, End, ObjectQueryParams);
AActor* HitActor = Hit.GetActor();
if(HitActor)
{
//检查碰撞的物体是不是可交互的
if (HitActor->Implements<UGameplayInterface>())
{
APawn* MyPawn = Cast<APawn>(MyOwner);
//执行交互的函数,Execute_+接口函数名
//第一个参数是被调用的Actor,后面的参数是交互接口的参数列表
IGameplayInterface::Execute_Interact(HitActor, MyPawn);
}
}
}
然后将这一组件添加到角色中去
//PlayerCharacter.h
protected:
UPROPERTY(VisibleAnywhere)
UInteractionComponent* InteractionComp;
void PrimaryInteract();
//PlayerCharacter.cpp
APlayerCharacter::APlayerCharacter()
{
InteractionComp = CreateDefaultSubobject<UInteractionComponent>("InteractionComp")
}
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAction("PrimaryInteract",IE_Pressed,this,&APlayerCharacter::PrimaryInteract);
}
void APlayerCharacter::PrimaryInteract()
{
InteractionComp->PrimaryInteract();
}
还有其他的碰撞查询的方法,上面用的是线性查询,但是如果要交互的东西非常小将会非常难受。
所以还有球体追踪的办法
TArray<FHitResult> Hits;
FCollisionShape Shape;
//设置碰撞的形状和大小
Shape.SetSphere(30.0f);
//这个函数当遇到第一个碰撞的物体的时候就会停下,然后收集所有的重叠部分
//前三个参数和线性查询一样
//第四个参数是球体的旋转,但是查询中的旋转没有意义所以设置为空旋转
//第五个是碰撞检测的球体的形状,可以是盒子,圆柱体或者球体
GetWorld()->SweepMultiByObjectType(Hits, Start, End, FQuat::Identity, ObjectQueryParams, Shape);
for (FHitResult Hit:Hits)
{
AActor* HitActor = Hit.GetActor();
if(HitActor)
{
//检查碰撞的物体是不是可交互的
if (HitActor->Implements<UGameplayInterface>())
{
APawn* MyPawn = Cast<APawn>(MyOwner);
//执行交互的函数,Execute_+接口函数名
//第一个参数是被调用的Actor,后面的参数是交互接口的参数列表
IGameplayInterface::Execute_Interact(HitActor, MyPawn);
//碰撞检测实体化,调试时看下检测的方向是否正确
//第一个参数是当前的关卡,第二个参数是击中的点,第三个参数是半径
//第四个参数是段,就是绘制线的数量,第六个参数是持续事件
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, 30.0f, 32, FColor::Red, false, 2.0f);
break;
}
}
}
DrawDebugLine(GetWorld(), Start, End, FColor::Red, false, 2.0f, 0.0f, 2.0f);