Today, I will

[Unreal] 라인트레이스 C++ 본문

Unreal

[Unreal] 라인트레이스 C++

Lv.Forest 2024. 1. 27. 17:04

언리얼에서 라인트레이스 기능을 C++로 작성해보도록 한다.

 

 

캐릭터가 라인트레이스로 찾을 액터를 하나 C++로 만들어주고, 블루프린트를 만들어준다.

.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TraceTestActor.generated.h"

UCLASS()
class LECTURE_API ATraceTestActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATraceTestActor();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere, Category = "MySettings")
	class UBoxComponent* boxComp;

	UPROPERTY(VisibleAnywhere, Category = "MySettings")
	class UStaticMeshComponent* meshComp;
};

.ccp

#include "TraceTestActor.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
// Sets default values
ATraceTestActor::ATraceTestActor()
{
 	// 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;

    boxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComponent"));
    RootComponent = boxComp;
    boxComp->SetBoxExtent(FVector(50));
    boxComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    boxComp->SetCollisionObjectType(ECC_WorldDynamic);

    meshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
    meshComp->SetupAttachment(RootComponent);
    boxComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

// Called when the game starts or when spawned
void ATraceTestActor::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void ATraceTestActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

 

트레이스 채널을 하나 추가해준다.

 

DefaultEngine.ini에서 방금 추가한 채널명을 검색하여 확인해준다.(플레이어 코드에서 필요)

블루프린트 메쉬를 적당히 지정해주고

collisin 설정이 Block인지 필히 확인해준다.

 

이제 캐릭터에서 라인트레이스할 함수를 만들어준다.

.h

protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	// To add mapping context
	virtual void BeginPlay();

	virtual void Tick(float DeltaSeconds);

public:
	/** Returns CameraBoom subobject **/
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
	/** Returns FollowCamera subobject **/
	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }

	// 1. 캐릭터의 피벗을 기준으로 정면 방향 15미터 이내에 있는 ATraceActor 액터 중에 가장 가까운 액터를 액터의 이름을 출력하는 함수를 선언하고, Tick에서 호출하고 싶다.
	void SensorForNearestsActor();
    // 2. 캐릭터의 피벗을 기준으로 전방 15미터 이내의 ATraceActor 액터 전부를 출력하는 함수를 선언하고, Tick에서 호출하고 싶다.
	void SensorForAllActor();

.cpp

코드 스타일1이 주석처리되어 있고,, 코드스타일2가 작성되어 있는데

둘 다 정상 작동하는 코드이다.

#include "TraceTestActor.h"

void ALectureCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	SensorForAllActor();
}

void ALectureCharacter::SensorForNearestsActor()
{
	/* 코드스타일1
	FHitResult hit;
	FVector pos = GetActorLocation();
	FVector StandardVec = (FRotationMatrix(GetActorRotation()).GetUnitAxis(EAxis::X) * 1500.0f) + pos;
	FCollisionQueryParams traceParam;
	traceParam.AddIgnoredActor(this);
	if (GetWorld()->LineTraceSingleByChannel(hit, pos, StandardVec, ECC_GameTraceChannel1, traceParam)) {
		UE_LOG(LogTemp, Warning, TEXT("%s"), *hit.GetActor()->GetActorNameOrLabel());
		DrawDebugLine(GetWorld(), pos, hit.ImpactPoint, FColor::Green, false, 0, 0, 2.0f);
		DrawDebugBox(GetWorld(), hit.ImpactPoint, FVector(5.0f), FColor::Green, false, 0, 0, 1.5f);
	}
	else {
		DrawDebugLine(GetWorld(), pos, StandardVec, FColor::Green, false, 0, 0, 2.0f);
	}
	*/
	// 1. 캐릭터의 피벗을 기준으로 정면 방향 15미터 이내에 있는 ATraceActor 액터 중에 가장 가까운 액터를 액터의 이름을 출력하고 싶다.
	
	// 코드스타일2
	FHitResult* hit = new FHitResult;
	FVector pos = GetActorLocation();
	FVector frontVec = GetActorForwardVector();
	FVector StandardVec = (frontVec * 150.0f) + pos;
	
	FCollisionQueryParams* traceParam = new FCollisionQueryParams;
	//DrawDebugLine(GetWorld(), pos, StandardVec, FColor::Green, false, 2.0f);

	if (GetWorld()->LineTraceSingleByChannel(*hit, pos, StandardVec, ECC_GameTraceChannel1, *traceParam)) {
		DrawDebugLine(GetWorld(), pos, StandardVec, FColor::Green, false, 2.0f);
		AActor* a  = hit->GetActor();
		ATraceTestActor* TraceTestActor = Cast<ATraceTestActor>(a);
		if (TraceTestActor) {
			UE_LOG(LogTemp, Warning, TEXT("%s"), *TraceTestActor->GetActorNameOrLabel());
		}
	}
	//참조자로 받는데 const로 받지 않는다는건 십중팔구 값을 채워주겠다는 뜻
}


void ALectureCharacter::SensorForAllActor()
{
	// 2. 캐릭터의 피벗을 기준으로 전방 15미터 이내의 ATraceActor 액터 전부를 출력하고 싶다.
	//LineTraceMultiByChannel
	TArray<FHitResult> hits;
	FVector pos = GetActorLocation();
	FVector StandardVec = (FRotationMatrix(GetActorRotation()).GetUnitAxis(EAxis::X) * 1500.0f) + pos;
	FCollisionQueryParams traceParam;
	traceParam.AddIgnoredActor(this);
	if (GetWorld()->LineTraceMultiByChannel(hits, pos, StandardVec, ECC_GameTraceChannel1, traceParam)) {
		FVector newStartVec = pos;

		for (const FHitResult& hit : hits) {
			UE_LOG(LogTemp, Warning, TEXT("Mmulti test: %s"), *hit.GetActor()->GetActorNameOrLabel());
			DrawDebugLine(GetWorld(), newStartVec, hit.ImpactPoint, FColor::Green, false, 0, 0, 1.5f);
			newStartVec = hit.ImpactPoint;
			DrawDebugBox(GetWorld(), hit.ImpactPoint, FVector(5.0f), FColor::Green, false, 0, 0, 1.5f);
		}
	}
	else {
		DrawDebugLine(GetWorld(), pos, StandardVec, FColor::Red, false, 0, 0, 1.5f);
	}
}

멀티라인트레이스에서 Sweep로 Line과 비슷하다.

월드 상에 액터를 아래와 같이 배치하였을 때

라인트레이스 return 지점은 collision Block check여야 한다는 점을 유념해야 한다.

즉, mult 라인 트레이스를 쏘기 위해서는 마지막 액터만 block이고 앞의 두 액터는 overlap

 

 

멀티 라인트레이스 캡처 예시

 

사실 여러개를 식별하는 것은 라인트레이스 보다 오버랩이 용이하다.(라인멀티나 스윕멀티는 많이 쓰지 않음)

// 범위 안에 있는 block, overlap을 전부 체크(다만 block이 있어야 함)
void ALectureCharacter::SensorUsingOverlap()
{
	// 영역의 안 밖을 체크함
	TArray<FOverlapResult> hitResults;
	FVector centerLoc = GetActorLocation();
	// 쿼터니언과 로테이션은 서로 변환 가능
	//FQuat playerRot = GetActorRotation().Quaternion();
	//GetActorQuat().Rotator();
	FQuat playerRot = GetActorQuat();
	FCollisionQueryParams traceParam;
	traceParam.AddIgnoredActor(this);
	// 반지름 범위 안에 오버랩되는 것 중 채널 안의 애들 감지
	if (GetWorld()->OverlapMultiByChannel(hitResults, centerLoc, playerRot, ECC_GameTraceChannel1, FCollisionShape::MakeSphere(1500), traceParam)) {
		for (const FOverlapResult hitresult : hitResults) {
			UE_LOG(LogTemp, Warning, TEXT("Overlap Test: %s"), *hitresult.GetActor()->GetActorNameOrLabel());
			DrawDebugSphere(GetWorld(), hitresult.GetActor()->GetActorLocation(), 50, 10, FColor::Green, false, 0, 0, 1.5f);
		}
	}
	DrawDebugSphere(GetWorld(), GetActorLocation(), 500, 10, FColor::Green, false, 0, 0, 1.5f);
}