Showing

[Unreal] Line Trace 블루프린트와 C++로 구현 본문

Unreal

[Unreal] Line Trace 블루프린트와 C++로 구현

RabbitCode 2023. 11. 24. 11:08

CS384G - 프로젝트 2: 레이 트레이싱 (utexas.edu)

 

CS384G - Project 2: Ray Tracing

Details of the Trace project debugging user interface One of the useful tools we are providing with the Trace project is a debugging interface, which you can use to experiment with tracing individual paths through the scene and visualize exactly what is go

www.cs.utexas.edu

Line Trace를 사용하면 눈에 보이는 웬만한 것들을 캐치할 수 있다.

Collision Shape (Box , Sphere ) 성능 때문에, Line > Sphere > Box

 

블루프린트

콜리전에서 Object Channels와 Trace Chaanel로 구분되어 있는 것을 볼 수 있다. 이는 언리얼 개발진들이 경험적으로 쌓은 노하우이다.

개발자는 오브젝트마다 채널을 달 수도 있고 트레이스 라인에 따라 반응을 어떻게 할지 결정함으로써 라인트레이스를 사용할 수도 있을 것이다.

single line

플레이어로부터 1500 거리 정도 되는 레이트레이싱을 쏘는 예시

Trace Chaanel

start로 액터의 위치를 지정하고 액터의 앞방향 벡터에 거리만큼 곱한 값을 더한 위치를 End로 지정한다. 

Object Channels

Make Array 목록은 만들어둔 콜리젼 오브젝트 채널

 

Ignore Self는 끌 일이 없다.

Out Hit는 끝점에 대한 데이터를 가지고 있는 구조체이다.

 

 

break는 ray에 부딪힌 것들의 정보를 분리해서 가져올 수 있다. 예시에서는 부딪힌 액터의 이름을 가져와서 print 해준 예제

 

 

배열 아이콘

 

mult line

확대

mult는 충돌결과를 여러개 가지고 오고 싶을 때 쓰면 된다.

즉 선 안에 들어오는 것들을 array로 가져와 알 수 있다는 뜻이다.

 

 

box 스타일

sphere 스타일

이번에는 카메라와 카메라arm을 이용해서 플레이어의 위치를 지정해보도록 한다.

우선 코드를 아래와 같이 변경해준다.

확대샷

 

shere 레이를 캡처하기 위해 1키를 누른 뒤 바로 이동하였다. 공중에 캐릭터가 카메라 방향에 맞추어서 레이를 쏜 흔적이 남아있다.

c++

E를 눌렀을 때 라인트레이싱이 나올 수 있도록 C++로 작성해보도록 한다.

 

 

 

플레이어의 시점을 반환, AI의 경우 이는 Pawn의 '눈' 시점을 의미한다. (인간) 플레이어의 경우 이는 카메라의 시점을 의미

GetController()->GetPlayerViewPoint(_Location, _Rotation);

 

플레이어 h에서 e관련 프로퍼티와 함수 추가

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "MyPlayer.generated.h"

class UInputMappingContext;
class UInputAction;
class UNiagaraComponent;

UCLASS()
class THIRDPERSONTEMPLATE_API AMyPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyPlayer();

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

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

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	UPROPERTY(EditAnywhere, Category = "Anim")
	UAnimMontage* attackMontage;

public:
	UPROPERTY(VisibleAnywhere, Category="Camera")
	class USpringArmComponent* springArmComp;

	UPROPERTY(VisibleAnywhere, Category = "Camera")
	class UCameraComponent* cameraComp;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputMappingContext* PlayerMappingContext;

	UPROPERTY(VisibleAnywhere, Category = "Weapon")
	class UStaticMeshComponent* staffMesh;

	UPROPERTY(EditAnywhere, Category = "FX")
	class UNiagaraComponent* jumpNiagara;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* MoveIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* LookUpIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* TurnIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* JumpIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* FireIA;

	UPROPERTY(EditAnywhere, Category = "Input")
	UInputAction* InteractionIA;

public:
	void Move(const FInputActionValue& Value);
	void LookUp(const FInputActionValue& Value);
	void Turn(const FInputActionValue& Value);
	void InputJump(const FInputActionValue& Value);
	void InputFire(const FInputActionValue& Value);
	void InteractionPositive(const FInputActionValue& Value);
	void Fire();
	float fireTimerTime; // 타이머의 시간
	bool fireReady; // 타이머 도달 여부 
public:
	UPROPERTY(EditAnywhere, Category = "Move")
	float moveSpeed;

	UPROPERTY(EditAnywhere)
	TSubclassOf<class APBullet> bulletFactory;

	UPROPERTY(EditAnyWhere)
	float fireCoolTime = 2.0f;

	bool isJump = false;

private:
	FVector moveDirection;

protected:
	void FireCoolTimer(float cooltime, float deltaTime);
	void ShowFX(bool show);
};
void AMyPlayer::InteractionPositive(const FInputActionValue& Value)
{
	//start와 end 가 중요
	FVector _Location;
	FRotator _Rotation;
	FHitResult _HitOut;

	//카메라가 보고 있는 위치
	GetController()->GetPlayerViewPoint(_Location, _Rotation);
	FVector _Start = _Location;
	int ViewDis = 2000;
	FVector _End = (_Rotation.Vector() * ViewDis); // 2000은 무작위 거리
	FCollisionQueryParams _traceParams; //trace의 params들
	GetWorld()->LineTraceSingleByChannel(_HitOut, _Start, _End, ECC_Visibility, _traceParams); // 충돌 결과, 시작점, 결과, 콜리전채널, params(안넣으면 디폴트)
	DrawDebugLine(GetWorld(), _Start, _End, FColor::Green, false, 2.0f);
}

전체 코드

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

#include "MyPlayer.h"
#include "Components/ArrowComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputSubsystems.h" //https://docs.unrealengine.com/5.0/en-US/enhanced-input-in-unreal-engine/
#include "EnhancedInputComponent.h" //https://docs.unrealengine.com/4.26/en-US/InteractiveExperiences/Input/EnhancedInput/
#include "PBullet.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "DrawDebugHelpers.h"

// Sets default values
AMyPlayer::AMyPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	//ConstructorHelpers 라는 유틸리티로 에셋 가져오기(찾기)
	// InitMesh 굳이 이렇게 하기는 했으나 콘텐츠 브라우저에서 선택하는게 사실 더 좋음
	ConstructorHelpers::FObjectFinder<USkeletalMesh> InitMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/MyResouce/unitychan.unitychan'"));
	if (InitMesh.Succeeded()) { // 제대로 오브젝트를 가져왔다면~
		// GetMesh는 actor(근본)의 상속자인 character에서 제공!
		GetMesh()->SetSkeletalMesh(InitMesh.Object);
		GetMesh()->SetRelativeLocationAndRotation(
			FVector(0, 0, -88)
			, FRotator(0, -90, 0));
	}
	springArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
	springArmComp->SetupAttachment(RootComponent);
	springArmComp->SetRelativeLocationAndRotation(FVector(0, 0, 50), FRotator(-20,0,0));
	springArmComp->TargetArmLength = 530;
	springArmComp->bUsePawnControlRotation = true;

	//카메라도 생성 초기화
	cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	cameraComp->SetupAttachment(springArmComp);
	cameraComp->bUsePawnControlRotation = false;

	//staff mesh
	staffMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("staff meshComp"));// 스태프 외관 컴포넌트 등록
	// 두번째 인자 캐릭터 메쉬에 부착, FName이 문자열 속도가 빠름
	staffMesh->SetupAttachment(GetMesh(), FName("Character1_RightHandSocket"));
	//staffMesh->SetRelativeLocationAndRotation

	//pawn이 들고 있음
	bUseControllerRotationYaw = true;
	moveSpeed = 100.0f;

	fireTimerTime = 0;
	fireCoolTime = 1.85f;
	fireReady = true;    // 발사 준비 On
}

// Called when the game starts or when spawned
void AMyPlayer::BeginPlay()
{
	Super::BeginPlay();
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem
			= ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer())) {
			Subsystem->AddMappingContext(PlayerMappingContext, 0);
		}
	}
	jumpNiagara = GetComponentByClass<UNiagaraComponent>();
}

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

	if (fireReady == false) {
		FireCoolTimer(fireCoolTime, DeltaTime);
	}

	if (GetCharacterMovement()->IsFalling()) {
		ShowFX(true);
	}
	else {
		ShowFX(false);
	}

	const TArray<USceneComponent*>& AttachChildren = GetRootComponent()->GetAttachChildren();
	const int32 Count = AttachChildren.Num();


	// 이동방향을 컨트롤 방향 기준으로 변환
	moveDirection = FTransform(GetControlRotation()).TransformVector(moveDirection);

	/*
	// 플레이어 이동 - 등속운동
	FVector P0 = GetActorLocation();//현재 위치
	FVector vt = moveDirection * moveSpeed * DeltaTime; //이동거리
	FVector P = P0 + vt;
	SetActorLocation(P);
	*/

	AddMovementInput(moveDirection);

	// 방향 초기화
	// 방향이 누적되지 않게
	moveDirection = FVector::ZeroVector;
}

// Called to bind functionality to input
void AMyPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
		EnhancedInputComponent->BindAction(MoveIA, ETriggerEvent::Triggered, this, &AMyPlayer::Move);
		EnhancedInputComponent->BindAction(LookUpIA, ETriggerEvent::Triggered, this, &AMyPlayer::LookUp);
		EnhancedInputComponent->BindAction(TurnIA, ETriggerEvent::Triggered, this, &AMyPlayer::Turn);
		EnhancedInputComponent->BindAction(JumpIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputJump);
		EnhancedInputComponent->BindAction(FireIA, ETriggerEvent::Triggered, this, &AMyPlayer::InputFire);
		EnhancedInputComponent->BindAction(InteractionIA, ETriggerEvent::Started, this, &AMyPlayer::InteractionPositive);
	}

}

void AMyPlayer::Move(const FInputActionValue& Value)
{
	//UE_LOG(LogTemp, Log, TEXT("Log Message %f"), moveSpeed);
	const FVector _currentValue = Value.Get<FVector>();
	if (Controller) {
		moveDirection.Y = _currentValue.X;
		moveDirection.X = _currentValue.Y;
	}
	//FVector newLocation = GetActorLocation() + _currentValue;
	//SetActorLocation(newLocation);
}

void AMyPlayer::LookUp(const FInputActionValue& Value)
{
	// mouse y - 한 축의 값(float)
	const float _currentValue = Value.Get<float>();
	APawn::AddControllerPitchInput(_currentValue);
}

void AMyPlayer::Turn(const FInputActionValue& Value)
{
	// mouse x - 한 축의 값(float)
	const float _currentValue = Value.Get<float>();
	APawn::AddControllerYawInput(_currentValue);
}

void AMyPlayer::InputJump(const FInputActionValue& Value)
{
	Jump();
}

void AMyPlayer::InputFire(const FInputActionValue& Value)
{
	if (fireReady == true) {
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
		if (AnimInstance) {
			AnimInstance->Montage_Play(attackMontage);
		}
		fireReady = false;
	}
}

void AMyPlayer::InteractionPositive(const FInputActionValue& Value)
{
	//start와 end 가 중요
	FVector _Location;
	FRotator _Rotation;
	FHitResult _HitOut;

	//카메라가 보고 있는 위치
	GetController()->GetPlayerViewPoint(_Location, _Rotation);
	FVector _Start = _Location;
	int ViewDis = 2000;
	FVector _End = (_Rotation.Vector() * ViewDis); // 2000은 무작위 거리
	FCollisionQueryParams _traceParams; //trace의 params들
	GetWorld()->LineTraceSingleByChannel(_HitOut, _Start, _End, ECC_Visibility, _traceParams); // 충돌 결과, 시작점, 결과, 콜리전채널, params(안넣으면 디폴트)
	DrawDebugLine(GetWorld(), _Start, _End, FColor::Green, false, 2.0f);
}

void AMyPlayer::FireCoolTimer(float cooltime, float deltaTime)
{
	if (fireTimerTime < cooltime) {
		fireTimerTime += deltaTime;
	}
	else
	{
		fireTimerTime = 0;
		fireReady = true;
	}
}

void AMyPlayer::ShowFX(bool show)
{
	if (nullptr == jumpNiagara) {
		jumpNiagara = GetComponentByClass<UNiagaraComponent>();
	}
	jumpNiagara->SetVisibility(show);
}

void AMyPlayer::Fire()
{
	FTransform firePosition = staffMesh->GetSocketTransform(TEXT("FirePosition"));
	GetWorld()->SpawnActor<APBullet>(bulletFactory, firePosition);
}