[UE5] PhysicsHandle로 액터 잡기

kkado·2024년 3월 28일
0

UE5

목록 보기
26/61
post-thumbnail

트레이스를 이용해서 시선이 향하는 방향의 액터를 식별할 수 있었고, 블루프린트와 C++를 연결해서 매핑한 키를 입력했을 때 C++ 함수가 실행되도록 만들었다.

이제 액터를 바라보고 잡기 키(F)를 눌렀을 때 액터를 집어들 수 있는 기능을 구현해 보고자 한다.


Physics Handle 컴포넌트

컴포넌트 부착

먼저 물리(Physics)가 적용된 액터와 상호작용할 수 있는 액터 컴포넌트인 Physics Handle에 대해서 알아야 한다.

액터를 잡거나, 놓거나, 물리학과 관련된 데이터를 설정할 수 있다.

먼저 Player 블루프린트에서 피직스핸들 컴포넌트를 추가한다.
다른 컴포넌트에 부착되는 Scene Component가 아니라 Actor Component임을 확인할 수 있다.

C++에서 인스턴스 가져오기

이제 생성한 피직스핸들을 C++에서 가져와야 한다.
코드 재사용성을 위해 별도의 함수로 만들었다. GetOwner()->FindComponentByClass<UPhysicsHandleComponent>() 을 보면 FindComponentByClass 라는 템플릿 함수가 있는데 여기에다 UPhysicsHandleComponent를 지정해 주면 피직스핸들 컴포넌트를 가져올 수 있다.

만약 피직스핸들이 존재하지 않는 등의 에러가 뜰 경우 nullptr을 갖게 되는데 이 상태로 값을 사용하게 되면 nullptr 에러가 발생할 수 있다는 점을 유념해서 예외처리한다. 이 함수의 리턴값을 받는 지점에서 예외처리를 해 주도록 하고, 본 함수 내부에서는 Warning 로그만 출력하도록 한다.

UPhysicsHandleComponent* UGrabber::GetPhysicsHandle() const
{
	UPhysicsHandleComponent* result = GetOwner()->FindComponentByClass<UPhysicsHandleComponent>();
	if (result == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("No Physics Handle."));
	}
	return result;
}

관련 함수 찾기

이제 레퍼런스 문서에서 사용할 수 있을 만한 함수를 찾아야 한다.

우리가 필요로 하는 기능은 트레이스로 찾은 액터를 식별해서 들어올리는 기능이다.

Grab Component ~ 로 시작하는 함수를 찾을 수 있었다.
액터를 들어올렸을 때 외부 물리적 요인에 의해서 액터가 빙글빙글 회전하는 것보다는 고정되어 있는 것이 '잡았다' 라는 느낌을 줄 수 있으므로, GrabComponentAtLocationWithRotation 함수를 사용하면 될 듯 하다.

이 함수는 UPrimitiveComponent 형 포인터를 대상 컴포넌트로서 받고 있다. 그러면 트레이스의 결과로 돌려받는 FHitResult 클래스를 살펴보며 사용할 만한 데이터가 있는지 찾아본다.

GetComponent() 함수의 리턴값이 UPrimitiveComponent 인 것을 확인했다.


ImpactPoint vs Location

필요한 또다른 파라미터인 GrabLocation (FVector) 을 찾아야 한다.
HitResult를 살펴본 결과 ImpactPoint, Location이 그럴듯해 보이는데 두 가지 중에 어떤 값을 취해야 하는지 잘 가늠이 가질 않는다.

Grab 함수를 실행했을 때 두 지점 각각에 서로 다른 색깔의 구를 그려 보면서 어느 위치를 사용하는 것이 더 적절할 지 디버깅해 볼 수 있다.

이전에 사용했던 DrawDebugLine 함수와 비슷하게 구를 만들어 주는 DrawDebugSphere 함수를 사용할 수 있다.

관련 문서 링크

DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10, 10, FColor::Red);
DrawDebugSphere(GetWorld(), HitResult.Location, 10, 10, FColor::Green);

결과를 확인하면 둘의 차이를 확연히 알 수 있다.

트레이스를 수행할 때 Radius가 100인 구를 이용했다는 점을 상기하면 좋다.

  • Location을 표시하는 초록 구의 경우 액터 외부에 나타났다. 즉 Radius가 100인 구가 트레이스를 수행할 때 특정 액터와 충돌했을 시점의 '구의 중심 좌표' 를 가지고 있는 데이터임을 알 수 있다.
  • 반면 ImpactPoint를 표시하는 빨간 구의 경우 액터 상에 나타났다. 이것을 통해 트레이스 물체와 충돌한 '액터 상의 지점' 을 가지고 있다는 것을 알 수 있다.

즉 특정한 물체를 '잡았다' 라고 하려면 ImpactPoint를 잡는 Location으로 설정하는 것이 타당할 것이다.


고로 다음과 같이 GrabComponentAtLocationWithRotation 함수를 작성한다.

	FCollisionShape Sphere = FCollisionShape::MakeSphere(GrabRadius);
	FHitResult HitResult;
	bool rv = GetWorld()->SweepSingleByChannel(
		HitResult, 
		Start, 
		End, 
		FQuat::Identity, 
		ECC_GameTraceChannel2, 
		Sphere
	);

	if (rv)
	{
		PhysicsHandle->GrabComponentAtLocationWithRotation(
			HitResult.GetComponent(),
			NAME_None,
			HitResult.ImpactPoint,
			GetComponentRotation()
		);
	}

TargetLocation 설정

이제 액터를 그랩 했을 때, 잡고 있는 액터의 위치를 지정해 주어야 한다.
API 문서로부터 SetTargetLocationAndRotation ( FVector NewLocation, FRotator NewRotation ) 이라는 함수를 확인했다.

집어서 들고 있는 거리는 집을 수 있는 최대 사거리보다 짧은 것이 보기 좋을 것 같으므로, 최대 사거리의 절반인 200으로 변수를 설정한다.

로케이션을 지정하는 것은 End 벡터를 설정했을 때와 크게 다르지 않다. 현재 위치를 가지고 Forward 벡터를 얻은 후 HoldDistance 를 곱해주면 될 것이다.

그리고 이러한 TargetLocation은 플레이어 액터가 움직임에 따라 바뀌어야 한다. 그래야 잡고 이동할 때 따라올 것이다. 따라서 TickComponent 함수 아래에 작성해 준다.

FVector HoldLocation = GetComponentLocation() + GetForwardVector() * HoldDistance;
PhysicsHandle->SetTargetLocationAndRotation(HoldLocation, GetComponentRotation());

결과

액터에 다가가 F 키를 눌러 액터를 집어들고, 이리저리 이동할 수 있는 것을 확인할 수 있다.

profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글