虚幻引擎的BoxComponent组件实现触发器/碰撞箱

在虚幻引擎中,BoxComponent是一种碰撞组件,它定义了一个长方体形状的边界,用于检测碰撞和重叠事件。它通常用于构建触发器区域,即当其他物体进入或离开这个长方体区域时,会触发相应的事件。

BoxComponent的触发器功能: 当将BoxComponent设置为触发器(即不阻挡物体,但检测重叠)时,它可以用来检测其他碰撞体是否进入或离开它的区域。触发器不会阻挡物体移动,但会发送重叠事件(OnComponentBeginOverlap和OnComponentEndOverlap),从而允许我们在蓝图中或C++中编写逻辑来响应这些事件。

触发器功能在很多游戏场景中非常有用,例如:

  1. 陷阱区域:当玩家进入一个区域时,触发陷阱。

  2. 检查点:在赛车游戏中,当车辆通过检查点时,记录时间或位置。

  3. 门触发器:当玩家靠近门时,自动打开门。

  4. 拾取物品:当玩家经过一个物品时,自动拾取。

如何创建一个BoxComponent组件:

如何配置BoxComponent组件:

不知道为啥,这个BoxComponent组件创建后,很多基本的函数和定义不具备需要我们自己写,或者从其他的类中直接复制过来:

//TriggerComponent.h
#pragma once

#include "CoreMinimal.h"
#include "Components/BoxComponent.h"
#include "TriggerComponent.generated.h"

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class DUNGEONESCAPE_API UTriggerComponent : public UBoxComponent
{
	GENERATED_BODY()
public:
	/*此构造函数用于初始化,只调用一次,初始化的方式还有很多种:
    在构造函数初始化,在beginPlay()里面初始化,在UE的细节面板初始化
    它们之间的执行顺序是:
    构造函数 -> 细节面板(属性覆盖)-> BeginPlay()*/
    UTriggerComponent();
protected:
	// 此组件的初始化
	virtual void BeginPlay() override;

public:
	/*此函数每帧调用,;必须在构造函数里面
    写了PrimaryComponentTick.bCanEverTick = true;才能使用*/
	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
		FActorComponentTickFunction* ThisTickFunction) override;
}
//TriggerComponent.cpp

UTriggerComponent::UTriggerComponent()
{
	/*将此组件设置为在游戏启动时初始化,并在每一帧都进行更新。
    如果您不需要这些功能,可以将其关闭以提高性能。*/
	PrimaryComponentTick.bCanEverTick = true;

	UE_LOG(LogTemp, Display, TEXT("Trigger component is constructed"));

}

void UTriggerComponent::BeginPlay()
{
	Super::BeginPlay(); 
}

void UTriggerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

}

如何理解两个委托类实例:

OnComponentBeginOverlap委托: 其声明, 定义和实例化都在我们引用的祖宗头文件和继承的祖宗类里,无需我们自己操作,直接拿来用就行, 其作用就是存放函数并在触发时调用函数集合;触发的条件是物体重合;检测触发的是UE的物理引擎PhysX,它会检测场景中所有的激活的碰撞组件,这个物理引擎无需程序猿自己调用.

OnComponentEndOverlap委托:  和上面类似,不过这个是在物体从重合状态变成非重合状态时调用的.

OnComponentBeginOverLap委托是一个类的实例,其类的定义如下:

// 委托的固定签名(你不能改变)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(
FComponentBeginOverlapSignature, //委托类名

UPrimitiveComponent* OverlappedComponent, //参数1
AActor* OtherActor,                       //参数2
UPrimitiveComponent* OtherComp,           //参数3
int32 OtherBodyIndex,                     //参数4
bool bFromSweep,                          //参数5
const FHitResult& SweepResult             //参数6

);

//创建一个实例
FComponentBeginOverlapSignature OnComponentBeginOverlap;
OverlappedComponent发出重叠事件的组件(即拥有这个事件的组件)
OtherActor参与重叠的另一个Actor(不是当前组件的拥有者,而是另一个Actor)
OtherComp参与重叠的另一个组件(属于OtherActor)
OtherBodyIndex用于物理身体索引(在物理模拟中,多个身体可能共享一个组件,这个索引指定了是哪个身体)。
bFromSweep一个布尔值,表示重叠是否是由扫描(Sweep)引起的(扫描通常用于移动过程中检测碰撞),如果物体是走进来的为true,传送进来的为false
SweepResult如果bFromSweep为真,这个结构体包含了扫描命中的详细信息(比如命中位置、法线等)

响应函数的编写:

由于委托在定义的时候就强制规定了所有的函数都必须具备这6个参数, 所以即使你的函数用不到这六个参数,也必须在函数参数列表写入这六个参数.

//TriggerComponent.cpp

void UTriggerComponent::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	/*
	标记未用的参数,明确告诉编译器"此物无用"(若有警报参数未使用,可写此物以消警报)
    (void)OtherBodyIndex;  
    (void)bFromSweep;
    (void)SweepResult;
	*/
	if (Mover) {
		Mover->ShouldMove = true;
	}
}

void UTriggerComponent::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OherBodyIndex)
{
	if (Mover) {
		Mover->ShouldMove = false;
	}
}

//此函数在BeginPlay()调用即可完成绑定委托
void UTriggerComponent::BindDelegates(){
    OnComponentBeginOverlap.AddDynamic(this, &UTriggerComponent::OnOverlapBegin);
    OnComponentEndOverlap.AddDynamic(this, &UTriggerComponent::OnOverlapEnd);
}

简单的例子:

假设我们需要创建一个压力板,用来控制一个房间的门,压力板被触发--门上升打开,松开--门下降关闭.

 先解决门的问题:

我们的目的是用压力板控制门, 那么我们需要建立压力板和门的链接, 由于只需用压力板控制门, 门无需控制压力板, 所以我们只需在压力板核心组件---碰撞箱 内存放门的指针, 然后把门指针在蓝图中存放进去就可以建立单向链接了

门的实现已经完成,由一个Mover组件的参数ShouldMove控制,ShouldMove=true 开门,ShouldMove= false 关门;我们可以先获取到门Actor的指针,然后再在门的里面找Mover组件,然后可以得到Mover的指针,然后就可以用Mover指针控制ShouldMove变量了.
 

1. 在UTriggerComponent类里面定义两个成员变量AActor* MoverActor;UMover* Mover;

UPROPERTY(EditAnywhere)
AActor* MoverActor;

UMover* Mover;

2. 进入UE的UTriggerComponent面板找到MoverActor变量,然后把场景里的门绑定到这个变量;

3. 在BeginPlay()里,利用Mover = MoverActor->FindComponentByClass<UMover>();找到Mover指针

if (MoverActor != nullptr) 
{
	Mover = MoverActor->FindComponentByClass<UMover>();
	if (Mover != nullptr) 
    {
		UE_LOG(LogTemp, Display, TEXT("Successfully found the mover component"));
	}
	else 
    {
		UE_LOG(LogTemp, Display, TEXT("Mover is nullptr"));
	}
}
else 
{
	UE_LOG(LogTemp, Display, TEXT("MoverActor is nullptr"));
}

解决压力板的问题:

向委托OnComponentBeginOverlap和OnComponentEndOverlap中写入函数OnOverlapBegin()和OnOverlapEnd();

然后在场景内加入立方体用来搭载触发器组件:

在组件面板找到触发器:

碰撞箱调整:

图左 黄色物体就是碰撞箱:如果你没找到, 可能是和方块重叠了, 你需要把他从方块里拖出来, 然后你可以把方块弄扁, 让方块看起来像个压力板.

使其达到 图右 这个效果就可以了, 白色部分是方块被我们压扁了, 实际上触发效果的是方框部分, 白色部分只是装饰
 

响应函数编写:

虽然你已经创建了压力板碰撞箱组件, 并且把场景需要控制的物体的指针给到了碰撞箱组件, 但是目前你还不能直接使用这个压力板, 因为你还没为触发和离开碰撞箱组件绑定响应函数, 因此你需要写两个响应函数,具体函数怎么写, 我在委托讲解的文章里详细讲了, 因为这涉及到了委托的机制:
草履虫也能看懂的虚幻引擎的委托机制https://blog.csdn.net/Howrun777/article/details/155206084代码如下:

//TriggerComponent.cpp

void UTriggerComponent::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	/*
	标记未用的参数,明确告诉编译器"此物无用"(若有警报参数未使用,可写此物以消警报)
    (void)OtherBodyIndex;  
    (void)bFromSweep;
    (void)SweepResult;
	*/
	if (Mover) {
		Mover->ShouldMove = true;
	}
}

void UTriggerComponent::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OherBodyIndex)
{
	if (Mover) {
		Mover->ShouldMove = false;
	}
}

//此函数在BeginPlay()调用即可完成绑定委托
void UTriggerComponent::BindDelegates(){
    OnComponentBeginOverlap.AddDynamic(this, &UTriggerComponent::OnOverlapBegin);
    OnComponentEndOverlap.AddDynamic(this, &UTriggerComponent::OnOverlapEnd);
}

下面是全部代码:

//TriggerComponent.h


// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/BoxComponent.h"
#include "Mover.h"
#include "TriggerComponent.generated.h"




UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class DUNGEONESCAPE_API UTriggerComponent : public UBoxComponent
{
	GENERATED_BODY()

public:
	UTriggerComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
		FActorComponentTickFunction* ThisTickFunction) override;

	UPROPERTY(EditAnywhere)
	bool IsPressurePlate = false;

	UPROPERTY(EditAnywhere)
	AActor* MoverActor;

	UMover* Mover;

	UFUNCTION()
	void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
		UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult&
		SweepResult);

	UFUNCTION()
	void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
		UPrimitiveComponent* OtherComp, int32 OherBodyIndex);
};
//TriggerComponent.cpp


#include "TriggerComponent.h"

//构造函数(只调用一次,组件初始化时)
UTriggerComponent::UTriggerComponent()
{
	//这里bCanEverTick=true可以允许组件使用TickComponent()函数,每帧调用
	PrimaryComponentTick.bCanEverTick = true;
}

//此函数每帧调用
void UTriggerComponent::BeginPlay()
{
	Super::BeginPlay(); 

	if (MoverActor != nullptr) {
		Mover = MoverActor->FindComponentByClass<UMover>();
		if (Mover != nullptr) {
			UE_LOG(LogTemp, Display, TEXT("Successfully found the mover component"));
		}
		else {
			UE_LOG(LogTemp, Display, TEXT("Mover is nullptr"));

		}
	}
	else {
		UE_LOG(LogTemp, Display, TEXT("MoverActor is nullptr"));
	}
	UE_LOG(LogTemp, Display, TEXT("Trigger component is alive!"));

	//IsPressurePlate是一个bool类型的成员变量,用于控制是否开启压力板检测,本质是控制是否绑定门开启的函数
	if (IsPressurePlate) {
		OnComponentBeginOverlap.AddDynamic(this, &UTriggerComponent::OnOverlapBegin);
		OnComponentEndOverlap.AddDynamic(this, &UTriggerComponent::OnOverlapEnd);
	}
}

void UTriggerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

}

/*第一个绑定函数:此函数通过修改门的Mover组件的ShouldMove变量, 控制门的移动,
函数名可自定义,参数必须严格符合OnComponentBeginOverlap委托的定义*/
void UTriggerComponent::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	/*
	标记未用的参数,明确告诉编译器"此物无用"(若有警报参数未使用,可写此物以消警报)
    (void)OtherBodyIndex;  
    (void)bFromSweep;
    (void)SweepResult;
	*/
	if (Mover) {
		Mover->ShouldMove = true;
	}
}
//第二个绑定函数:函数名可自定义,参数必须严格符合OnComponentEndOverlap委托的定义
void UTriggerComponent::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OherBodyIndex)
{
	if (Mover) {
		Mover->ShouldMove = false;
	}
}

下面是关于门的Mover组件的代码:

//Mover.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Mover.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DUNGEONESCAPE_API UMover : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UMover();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
		FActorComponentTickFunction* ThisTickFunction) override;
	
	UPROPERTY(EditAnywhere)
	FVector MoveOffset;

	UPROPERTY(EditAnywhere)
	float MoveTime = 4.0f;

	UPROPERTY(EditAnywhere)
	bool ShouldMove = false;

	UPROPERTY(VisibleAnywhere)
	bool ReachedTarget = false;

	FVector TargetLocation;
	FVector StartLocation;

};
//Mover.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "Math/UnrealMathUtility.h"
#include "Mover.h"

// Sets default values for this component's properties
UMover::UMover()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// ...
}


// Called when the game starts
void UMover::BeginPlay()
{
	Super::BeginPlay();
	StartLocation = GetOwner()->GetActorLocation();
	// ...
	ShouldMove = false; // 强制设置为false
	TargetLocation = StartLocation;

}


// Called every frame
void UMover::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	UE_LOG(LogTemp, Display, TEXT("%d"),ShouldMove);

	if (ShouldMove==true) {
		TargetLocation = StartLocation + MoveOffset;
	}
	else if (ShouldMove == false) {		
		TargetLocation = StartLocation;
	}

	FVector CurrentLocation = GetOwner()->GetActorLocation();

	ReachedTarget = CurrentLocation.Equals(TargetLocation);


	if (ReachedTarget == false) {

		float Speed = MoveOffset.Length() / MoveTime;

		FVector NewLocation = FMath::VInterpConstantTo(CurrentLocation,
			TargetLocation, DeltaTime, Speed);
		GetOwner()->SetActorLocation(NewLocation);

		UE_LOG(LogTemp, Display, TEXT("%s is at location %s"),
			*GetOwner()->GetActorNameOrLabel(), *CurrentLocation.ToCompactString());
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值