虚幻引擎的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();

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

在组件面板找到触发器:

黄色物体就是碰撞箱:

哎呀太麻烦了,直接贴全部的代码吧...

//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());
	}
}

<think>我们正在讨论的是虚幻引擎C++中的碰撞设置。用户的问题是如何将BoxCollider(在UE中通常指的是UBoxComponent)的碰撞响应全部设置为重叠(Overlap)。在虚幻引擎中,碰撞响应分为三种:忽略(Ignore)、重叠(Overlap)和阻挡(Block)。设置碰撞响应需要操作两个部分:碰撞预设(Collision Preset)和碰撞响应(Collision Response)。 步骤: 1. 获取BoxCollider组件:通常我们会在C++类中通过CreateDefaultSubobject创建或者在蓝图中添加,然后在C++代码中获取该组件。 2. 设置碰撞预设:我们可以使用一个自定义的碰撞预设,或者直接修改组件的碰撞响应通道。 3. 设置所有通道的响应为重叠:我们可以遍历所有通道,将每个通道的响应设置为重叠,也可以直接设置组件的碰撞响应类型为重叠。 然而,在虚幻引擎中,通常我们通过设置碰撞预设(Collision Preset)来快速配置,但也可以在C++中逐个设置每个通道的响应。 有两种方法: 方法一:设置一个自定义的碰撞预设(例如在编辑器中创建),然后在代码中设置这个预设。 方法二:在代码中动态设置每一个通道的碰撞响应。 这里我们使用方法二,因为用户要求将所有碰撞响应都设置为重叠(包括所有通道)。 具体步骤: - 获取到UBoxComponent组件指针。 - 调用SetCollisionResponseToAllChannels(ECR_Overlap)函数,将所有通道的响应设置为重叠。 - 注意:这样设置后,该组件将不会阻挡任何物体,而是与所有物体发生重叠事件。 代码示例: 假设我们有一个AActor派生类,我们在其中创建了一个UBoxComponent组件,并希望在构造函数中设置其碰撞响应。 在头文件中(例如MyActor.h): ``` #include "Components/BoxComponent.h" UCLASS() class AMyActor : public AActor { GENERATED_BODY() public: AMyActor(); protected: UPROPERTY(VisibleAnywhere, Category="Collision") UBoxComponent* BoxCollider; }; ``` 在源文件(MyActor.cpp)中: ``` #include "MyActor.h" AMyActor::AMyActor() { PrimaryActorTick.bCanEverTick = false; // 创建Box组件 BoxCollider = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxCollider")); RootComponent = BoxCollider; // 可选,设为根组件 // 设置碰撞响应:将所有通道的响应设置为重叠 BoxCollider->SetCollisionResponseToAllChannels(ECR_Overlap); // 注意:这样设置后,该盒子组件将不会阻挡任何通道,而是与所有通道发生重叠事件。 // 同时,我们可能需要设置碰撞预设(Collision Preset)为自定义,避免使用预设覆盖我们的设置。 // 为了确保,我们也可以设置碰撞预设为Custom(自定义) BoxCollider->SetCollisionProfileName(TEXT("Custom")); // 使用自定义预设,这样我们设置的响应才会生效 // 另外,如果我们想要组件能够触发重叠事件,还需要设置生成重叠事件(默认是开启的,但可以明确设置) BoxCollider->SetGenerateOverlapEvents(true); } ``` 注意: - SetCollisionResponseToAllChannels(ECR_Overlap) 将每一个通道的响应设置为重叠。 - 设置自定义碰撞预设("Custom")是为了避免使用引擎预设中可能已经定义好的响应,覆盖我们的设置。 但是,如果我们想要保留某个预设的其他设置(比如对象类型),而只修改响应,那么我们可以先获取当前的碰撞预设,然后修改响应,但通常直接设置所有通道响应并设置为自定义预设更直接。 另外,生成重叠事件(SetGenerateOverlapEvents)默认是开启的,但在某些情况下(比如在代码中动态改变)可能需要显式设置。 如果用户希望只对特定的通道设置重叠,而对其他通道忽略,那么就需要单独设置每个通道。但用户要求是“所有碰撞响应都设置为重叠”,所以上述方法满足要求。 引用:虚幻引擎的碰撞系统文档提到,碰撞响应决定了当两个物体碰撞时是忽略、重叠还是阻挡[^1]。 相关问题: 1. 如何设置特定通道(如Pawn)的碰撞响应为阻挡? 2. 如何创建一个自定义的碰撞预设(Collision Preset)并在代码中使用? 3. 在虚幻引擎中,重叠事件和阻挡事件有什么区别? 4. 如何检测两个物体之间的重叠事件? 5. 如何动态修改碰撞响应(例如在游戏运行时)? </think>在虚幻引擎 C++ 中将 `BoxCollider`(即 `UBoxComponent`)的碰撞响应全部设置为**重叠(Overlap)**,需通过以下步骤操作: --- ### 解决方法 #### 1. **获取碰撞组件指针** ```cpp // 在Actor类中声明(.h文件) UPROPERTY(VisibleAnywhere, Category="Collision") UBoxComponent* BoxCollider; ``` #### 2. **设置所有通道为OVERLAP** 在构造函数或初始化函数中执行: ```cpp // .cpp文件 AMyActor::AMyActor() { BoxCollider = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxCollider")); // 关键步骤:将所有碰撞通道设为重叠 BoxCollider->SetCollisionResponseToAllChannels(ECR_Overlap); // 确保生成重叠事件 BoxCollider->SetGenerateOverlapEvents(true); } ``` --- ### 关键参数说明 | **方法** | **作用** | **参数值** | |---------|---------|-----------| | `SetCollisionResponseToAllChannels()` | 全局设置所有碰撞通道 | `ECR_Overlap` | | `SetGenerateOverlapEvents()` | 启用/禁用重叠事件检测 | `true`(必需) | | `SetCollisionEnabled()` | 激活碰撞检测 | `ECollisionEnabled::QueryOnly` | > 完整设置示例: ```cpp BoxCollider->SetCollisionEnabled(ECollisionEnabled::QueryOnly); // 仅检测查询(不物理阻挡) BoxCollider->SetCollisionResponseToAllChannels(ECR_Overlap); // 全局重叠响应 BoxCollider->SetGenerateOverlapEvents(true); // 触发事件 ``` --- ### 技术原理 1. **碰撞响应类型** 虚幻引擎中碰撞响应分为三种: - `ECR_Ignore`:忽略碰撞 - `ECR_Overlap`:重叠(触发事件) - `ECR_Block`:阻挡(物理碰撞) 2. **重叠事件流程** 当设置为重叠后,以下事件会被触发: ```mermaid graph LR A[物体进入重叠区域] --> B[OnComponentBeginOverlap] A --> C[OnComponentEndOverlap] B --> D[执行自定义逻辑] C --> D ``` 3. **碰撞通道系统** 通过 `ECollisionChannel` 管理不同物体间的交互规则[^1]。 --- ### 注意事项 1. **与物理模拟的兼容性** - 若组件需参与物理模拟(如刚体),需保留部分通道为 `Block` - 纯检测用途(如触发器)可全设为 `Overlap` 2. **性能优化** 过度使用重叠检测可能影响性能,建议: ```cpp // 缩小检测范围 BoxCollider->SetBoxExtent(FVector(50, 50, 50)); // 限制检测对象 BoxCollider->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); ``` 3. **蓝图联动** 在 C++ 中设置后,蓝图中的碰撞预设将显示为 **Custom**: ```mermaid graph TB C++[C++设置] -->|同步| BP[蓝图编辑器] BP --> CollisionTab[碰撞设置] CollisionTab --> CustomPreset[预设:Custom] ``` --- ### 相关问题 1. 如何只对特定通道(如玩家角色)设置重叠响应? 2. 物理模拟和查询检测(Query)有什么区别? 3. 如何处理重叠事件并获取碰撞对象信息? 4. 动态修改碰撞响应(如运行时切换阻挡/重叠)的方法? 5. 如何优化大量重叠检测组件的性能?[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值