[ UE5 C++ ] Grabber 구현

LeeTaes·2023년 5월 11일

UE5_기능구현

목록 보기
1/2
post-thumbnail

특정 액터를 잡아 이동시킬 수 있는 간단한 Grab 기능을 구현하기


기본 세팅

  1. 트레이스 채널 추가 (Grabber[이름], Ignore)
  2. 그랩으로 움직일 액터 설정
    Grabber[Block] 및 오버랩 이벤트 생성 활성화
    Simulate Physics 활성화
  • SceneComponent 클래스를 생성하여 Character의 카메라에 부착시켜 시점을 기준으로 작동

Character에 컴포넌트 붙이기

private:
	// 변수 선언
    // 컴포넌트를 표현할 때는 EditAnywhere보다 VisibleAnywhere를 사용하기
    // EditAnywhere는 포인터 변수를 편집할 수 있기 때문에 사용 주의
	UPROPERTY(VisibleAnywhere)
	class UGrabber* Grabber;
   
	UPROPERTY(VisibleAnywhere)
	class UPhysicsHandleComponent* PhysicsHandle;
ATestACharacter::ATestACharacter()
{
	// 생성자에 추가
	Grabber = CreateDefaultSubobject<UGrabber>(TEXT("Grabber"));
	Grabber->SetupAttachment(FirstPersonCameraComponent);

	PhysicsHandle = CreateDefaultSubobject<UPhysicsHandleComponent>(TEXT("PhysicsHandle"));
}


Grabber.h

  • 그랩 최대 거리를 저장하는 변수 필요
  • 그랩 후 물체를 잡고 있을 거리를 저장하는 변수 필요
  • 충돌체(구체)의 반지름을 저장하는 변수 필요 (충돌 판정)
  • 액션 매핑을 위한 InputComponent
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "Grabber.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TESTA_API UGrabber : public USceneComponent
{
	GENERATED_BODY()

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

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

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

	/** 액션 바인딩 */
	void SetupInputComponent();

	/** 그랩 기능 - 액션 바인딩 */
	UFUNCTION()
	void Grab();

	/** 그랩 취소 - 액션 바인딩 */
	UFUNCTION()
	void Released();

private:
	/** 그랩 충돌체의 반지름 */
	UPROPERTY(EditAnywhere)
	float GrabRadius;
	/** 최대 그랩 길이(사거리) */
	UPROPERTY(EditAnywhere)
	float MaxGrabDistance;
	/** 잡은 물체와의 플레이어 사이의 거리 */
	UPROPERTY(EditAnywhere)
	float HoldDistance;

	UInputComponent* InputComponent;

	class UPhysicsHandleComponent* GetPhysicsHandle() const;

	bool GetGrabbableInReach(FHitResult& HitResult) const;
};

Grabber.cpp

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


#include "Grabber.h"

/** 월드를 찾기 위해 */
#include "Engine/World.h"
/** 디버그를 돕기 위해 */
#include "DrawDebugHelpers.h"
/** PhysicsHandleComponent를 사용하기 위해 */
#include "PhysicsEngine/PhysicsHandleComponent.h"


// Sets default values for this component's properties
UGrabber::UGrabber()
{
	// 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;

	// 변수 초기화
	GrabRadius = 10.0f;
	MaxGrabDistance = 1000.0f;
	HoldDistance = 1000.0f;
}


// Called when the game starts
void UGrabber::BeginPlay()
{
	Super::BeginPlay();

	SetupInputComponent();
}


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

	
	UPhysicsHandleComponent* PhysicsHandle = GetPhysicsHandle();

	/** 무언가를 잡은 상황이 아니면 Tick 컴포넌트에서 위치를 설정할 필요 X */
	if (PhysicsHandle && PhysicsHandle->GrabbedComponent)
	{
		/** TargetLocation = Grabber 컴포넌트의 위치 + (방향백터(1) * 잡은 물체와 플레이어 사이의 거리 */
		FVector TargetLocation = GetComponentLocation() + (GetForwardVector() * HoldDistance);
		// 타겟의 위치와 회전을 설정합니다.
		PhysicsHandle->SetTargetLocationAndRotation(TargetLocation, GetComponentRotation());
	}

}

void UGrabber::SetupInputComponent()
{
	InputComponent = GetOwner()->FindComponentByClass<UInputComponent>();
	if (InputComponent)
	{
		// 액션 매핑
		InputComponent->BindAction("Grab", IE_Pressed, this, &UGrabber::Grab);
		InputComponent->BindAction("Released", IE_Released, this, &UGrabber::Released);
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("%s missing Input Component component"), *GetOwner()->GetName())
	}
}

void UGrabber::Grab()
{
	UPhysicsHandleComponent* PhysicsHandle = GetPhysicsHandle();

	if (!PhysicsHandle)
	{
		UE_LOG(LogTemp, Warning, TEXT("UPhysicsHandleComponent Null!!!"));
		return;
	}

	// GetGrabbableInReach의 SweepSingleByChannel에 의해 충돌한 구조체를 저장할 변수
	FHitResult OutHit;
	bool bHasHit = GetGrabbableInReach(OutHit);

	if (bHasHit)
	{
		// 충돌한 액터의 이름 받아오기
		AActor* HitActor = OutHit.GetActor();
		UE_LOG(LogTemp, Warning, TEXT("%s"), *HitActor->GetActorNameOrLabel());

		DrawDebugSphere(GetWorld(), OutHit.ImpactPoint, 10.0f, 10, FColor::Red, false, 10.0f);
	
		// PrimitiveComponent는 물리 시뮬레이션을 수행함
		UPrimitiveComponent* HitComponent = OutHit.GetComponent();
		// PhysicsHandle에 반응하도록 컴포넌트를 깨우기 (시뮬레이터는 성능상의 문제로 슬립 상태에 있을 수 있음)
		HitComponent->WakeAllRigidBodies();

		// FHitResult.ImpactPoint : 충돌한 대상을 건드리는 지점을 의미 (충돌체의 모양과 상관 X)
		// FHitResult.Location : 대상과 충돌체가 만난 지점을 의미 (충돌체의 모양, 크기에 상관 O)

		/** 지정된 위치 및 회전에서 지정된 구성 요소를 잡습니다. 회전을 제한함 */
		PhysicsHandle->GrabComponentAtLocationWithRotation
		(
			// 충돌한 물체의 컴포넌트
			// OutHit.GetComponent(),
			HitComponent,
			// 충돌한 본의 이름 NAME_None - 스태틱 메시이기 때문에 넣어줄 필요 X
			NAME_None,
			// 충돌한 대상을 건드리는 지점을 의미 (충돌체의 모양과 상관 X)
			OutHit.ImpactPoint,
			// Grabber의 회전
			GetComponentRotation()
		);
	}
}

void UGrabber::Released()
{
	UPhysicsHandleComponent* PhysicsHandle = GetPhysicsHandle();

	if (!PhysicsHandle)
	{
		UE_LOG(LogTemp, Warning, TEXT("UPhysicsHandleComponent Null!!!"));
		return;
	}

	// 붙잡은 컴포넌트가 있다면?
	if (PhysicsHandle->GetGrabbedComponent() != nullptr)
	{
		// 붙잡은 컴포넌트를 놓아주기
		PhysicsHandle->ReleaseComponent();
		UE_LOG(LogTemp, Warning, TEXT("Realeasd Grabber"));
	}
}

UPhysicsHandleComponent* UGrabber::GetPhysicsHandle() const
{
	UPhysicsHandleComponent* Result = GetOwner()->FindComponentByClass<UPhysicsHandleComponent>();
	if (Result == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UPhysicsHandleComponent is Null!"));
	}
	return Result;
}

bool UGrabber::GetGrabbableInReach(FHitResult& HitResult) const
{
	/** DrawDebugLine 시작 위치 - Grabber 컴포넌트의 위치 */
	FVector Start = GetComponentLocation();
	/** DrawDebugLine 종료 위치 - 시작 위치 + (방향 벡터(1) * 최대 그랩 거리) */
	FVector End = Start + GetForwardVector() * MaxGrabDistance;
	/** DrawDebugLine 생성 */
	DrawDebugLine(GetWorld(), Start, End, FColor::Red, false);
	DrawDebugSphere(GetWorld(), End, 10.0f, 10, FColor::Blue, false, 5.0f);

	/** SweepSingleByChannel : 범위 내의 하나의 값을 충돌을 알려주는 함수 */
	/** SweepSingleByChannel의 결과를 반환받는 변수 - 충돌한 상대의 정보를 저장하는 구조체 */

	// FQuat::Identity : 회전값이 없다는 의미
	// ECollisionChannel을 확인하는 방법 : 프로젝트의 config 폴더 - DefaultEngine.ini에서 해당 트레이스 채널명 검색
	/** 충돌체의 모양을 지정해주기 */
	FCollisionShape Sphere = FCollisionShape::MakeSphere(GrabRadius);

	return GetWorld()->SweepSingleByChannel(
		HitResult,
		Start, End,
		FQuat::Identity,
		ECC_GameTraceChannel2,
		Sphere
	);
}

profile
클라이언트 프로그래머 지망생

0개의 댓글