Unreal Engine
UE5 Issues : C++ 충돌 처리
gbleem
2025. 1. 27. 19:52
게임을 만들면서, 충돌 처리는 많이 사용하게 되는 로직 중 하나이다.
발판을 밟으면 일정 시간 이후 떨어지는 로직을 구현하면서 공부한 내용을 정리해 보았다.
1. NotifyActorBeginOverlap
- 언리얼 레퍼런스
- 두 액터가 겹쳐지기 시작할 때 발동되는 함수이다. (예를들어, 플레이어가 트리거로 걸어 들어가는 경우)
- AActor 클래스를 상속받은 것들의 가상 함수로 제공되고, 아래와 같은 시그니처를 가진다.
virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
- 이 함수를 사용하려면, 해당 액터와 다른 액터 모두 컴포넌트에 bGenerateOverlapEvents가 true로 설정되어야 한다.
- Generate Overlap Events
- BP의 디테일 창에서 검색하면 다음과 같은 값이 있는 것을 볼 수 있다.
- 또한 C++ 코드에서도 다음과 같은 함수를 통해 설정해 줄 수 있다.
BoxCollision->SetGenerateOverlapEvents(true);
2. 사용 예제 및 Issues
먼저 문제가 생긴 이슈를 정리해 보자
목적은 캐릭터가 액터를 밟으면, 그때 이후로 특정 시간 이후로 액터가 떨어지는 것을 구현하는 것이다.
- 만든 액터는 static mesh하나를 가지고 있는 액터였다.
- 이때 아래처럼 함수를 구현한 후 사용한다면, 우리가 원하는 결과를 얻지 못한다. (overlap 을 출력하지 않는다!)
void AFallingPlatform::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
UE_LOG(LogTemp, Warning, TEXT("Overlap!"));
//player 체크
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
if (PlayerCharacter)
{
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AFallingPlatform::Falling, DelayTime, true);
}
}
- 그 이유는 해당 함수는 overlap 되었을 때 발동하는 함수이기 때문이다.
- 우리가 가진 Static Mesh는 처음 만들게 되면 아래와 같은 콜리전 프리셋이 있다.
- 아래와 같은 콜리전을 가지게 되면, 캐릭터는 해당 액터가 관통되지는 않지만, overlap 할 수는 없게 된다.
- 그렇기 때문에 위의 코드에서 원하는동작이 이루어 지지 않고, overlap이라는 로그도 뜨지 않는다!
- 그렇다면 다시 아래와 과 같이 콜리전을 수정하게 되면,
- 아래 사진처럼 overlap되면서 로그도 뜨지만, 캐릭터가 액터에 겹쳐버리게 된다.
해결책
- 해결책으로는 overlap을 하는 box collision을 추가로 만들어주면 된다.
- 아래와 같이 액터의 생성자에서 Box Collision을 만들고, generate overlap event를 true로 켜주면 된다.
BoxCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("Box Collision"));
BoxCollision->SetupAttachment(RootComponent);
//box collision 크기 조절
BoxCollision->SetRelativeLocation(FVector(0.f, 0.f, 10.f));
BoxCollision->SetBoxExtent(FVector(50.f, 50.f, 30.f));
BoxCollision->SetGenerateOverlapEvents(true);
- 우리가 원하는 동작을 하는 모습
3. OnComponentBeginOverlap
충돌을 처리할 때 NotifyActionBeginOverlap을 오버라이드 하는 방식이 아닌 다른 방식이 있다고 하여, 추가적으로 살펴보고, 코드를 작성해서 활용해 보았다.
- 언리얼 레퍼런스
- 무언가가(액터가 아니어도 괜찮음) 이 컴포넌트와 겹치기 시작할 때 호출되는 이벤트
- 이벤트를 생성하려면, Generate Overlap Event가 활성화 되어있어야 한다. (Notify Actor Begin Overlap과 동일)
- 그러나 이 이벤트는 컴포넌트 수준으로 동작하기 때문에 더 세밀한 조절이 가능하다.
- 액터와 액터의 overlap보다 더 작은 수준의 overlap 체크
- 또한 이 방식은 델리게이트를 통해 바인딩을 해서 사용하게 된다.
아직 사용법이 익숙하지 않아 아래와 같이 사용해 보았다.
캐릭터가 액터 위에 올라가면(overlap되면) 액터가 올라가고, overlap이 끝나면 액터가 내려오는 것 구현
- 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ElevatePlatform.generated.h"
class UBoxComponent;
UCLASS()
class SCC_CH3_6_API AElevatePlatform : public AActor
{
GENERATED_BODY()
public:
AElevatePlatform();
protected:
virtual void BeginPlay() override;
UFUNCTION()
void OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnOverlapEnd(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
public:
virtual void Tick(float DeltaTime) override;
void Elevate(float DeltaTime);
void Descened(float DeltaTime);
public:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Platform|Components")
UStaticMeshComponent* StaticMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Platform|Components")
UBoxComponent* BoxCollision;
private:
float MoveSpeed;
float MaxDistance;
bool CanElevate;
double StartPositionZ;
};
- cpp 파일
#include "ElevatePlatform.h"
#include "Components/BoxComponent.h"
#include "PlayerCharacter.h"
AElevatePlatform::AElevatePlatform()
:MoveSpeed(40.f)
,MaxDistance(200.f)
,CanElevate(false)
,StartPositionZ(0.f)
{
PrimaryActorTick.bCanEverTick = true;
StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Static Mesh"));
SetRootComponent(StaticMesh);
static ConstructorHelpers::FObjectFinder<UStaticMesh>MeshAsset(TEXT("/Game/Fantastic_Village_Pack/meshes/props/deco/SM_PROP_firepit.SM_PROP_firepit"));
if (MeshAsset.Succeeded())
{
StaticMesh->SetStaticMesh(MeshAsset.Object);
}
StaticMesh->SetRelativeScale3D(FVector(1.5f, 1.5f, 1.f));
BoxCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("Box Collision"));
BoxCollision->SetupAttachment(RootComponent);
BoxCollision->SetGenerateOverlapEvents(true);
BoxCollision->OnComponentBeginOverlap.AddDynamic(this, &AElevatePlatform::OnOverlapBegin);
BoxCollision->OnComponentEndOverlap.AddDynamic(this, &AElevatePlatform::OnOverlapEnd);
}
void AElevatePlatform::BeginPlay()
{
Super::BeginPlay();
StartPositionZ = GetActorLocation().Z;
}
void AElevatePlatform::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
if (PlayerCharacter)
{
CanElevate = true;
}
}
void AElevatePlatform::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
if (PlayerCharacter)
{
CanElevate = false;
}
}
void AElevatePlatform::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (CanElevate)
{
Elevate(DeltaTime);
}
if (!CanElevate)
{
Descened(DeltaTime);
}
}
void AElevatePlatform::Elevate(float DeltaTime)
{
if (GetActorLocation().Z < MaxDistance)
{
AddActorLocalOffset(FVector(0.f, 0.f, MoveSpeed * DeltaTime));
}
}
void AElevatePlatform::Descened(float DeltaTime)
{
if(GetActorLocation().Z > StartPositionZ)
{
AddActorLocalOffset(FVector(0.f, 0.f, -MoveSpeed * DeltaTime));
}
}
구현된 결과 영상