트레이스를 이용해서 시선이 향하는 방향의 액터를 식별할 수 있었고, 블루프린트와 C++를 연결해서 매핑한 키를 입력했을 때 C++ 함수가 실행되도록 만들었다.
이제 액터를 바라보고 잡기 키(F)를 눌렀을 때 액터를 집어들 수 있는 기능을 구현해 보고자 한다.
먼저 물리(Physics)가 적용된 액터와 상호작용할 수 있는 액터 컴포넌트인 Physics Handle에 대해서 알아야 한다.
액터를 잡거나, 놓거나, 물리학과 관련된 데이터를 설정할 수 있다.
먼저 Player 블루프린트에서 피직스핸들 컴포넌트를 추가한다.
다른 컴포넌트에 부착되는 Scene Component가 아니라 Actor Component임을 확인할 수 있다.
이제 생성한 피직스핸들을 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
인 것을 확인했다.
필요한 또다른 파라미터인 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인 구를 이용했다는 점을 상기하면 좋다.
즉 특정한 물체를 '잡았다' 라고 하려면 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()
);
}
이제 액터를 그랩 했을 때, 잡고 있는 액터의 위치를 지정해 주어야 한다.
API 문서로부터 SetTargetLocationAndRotation ( FVector NewLocation, FRotator NewRotation )
이라는 함수를 확인했다.
집어서 들고 있는 거리는 집을 수 있는 최대 사거리보다 짧은 것이 보기 좋을 것 같으므로, 최대 사거리의 절반인 200으로 변수를 설정한다.
로케이션을 지정하는 것은 End 벡터를 설정했을 때와 크게 다르지 않다. 현재 위치를 가지고 Forward 벡터를 얻은 후 HoldDistance
를 곱해주면 될 것이다.
그리고 이러한 TargetLocation은 플레이어 액터가 움직임에 따라 바뀌어야 한다. 그래야 잡고 이동할 때 따라올 것이다. 따라서 TickComponent
함수 아래에 작성해 준다.
FVector HoldLocation = GetComponentLocation() + GetForwardVector() * HoldDistance;
PhysicsHandle->SetTargetLocationAndRotation(HoldLocation, GetComponentRotation());
액터에 다가가 F 키를 눌러 액터를 집어들고, 이리저리 이동할 수 있는 것을 확인할 수 있다.