Game Development/Unreal Engine

[UE5] 물체 옮기기 기능 구현 (Grabber)

Dlaiml 2024. 5. 19. 12:38

어렸을 때 즐겨하던 게임인 샌드박스 장르의 게임인 게리모드에는 물체를 잡고 돌릴 수 있는 physics gun이라는 장비가 있었다. physics gun의 기능 중에서 물체를 클릭하여 잡고 옮기는 기능 언리얼 엔진에서 구현해보자. 

https://steamcommunity.com/sharedfiles/filedetails/?id=534888671

 

목표는 아래와 같은 사물을 마우스를 클릭하여 잡으면 플레이어 시점 앞으로 물체가 고정되고 놓았을 때 고정이 해제되면서 다시 물체에 물리법칙을 적용하는 것이다. 

 

 

Physics Handle Component

UE Remark: Utility object for moving physics around

Physics Handle Component는 물리가 적용되는 object를 이동시키기 위한 유틸리티 object이다.

구현하고자 하는 기능에 꼭 필요한 Component로 Character 클래스의 Component로 추가하였다.

 

Grabber

다음으로는 Character 클래스 내부에 Grab(잡기), Release(놓기) 로직을 담당하는 커스텀 클래스를 추가하자.

Attach가 가능하고 Transfom 정보가 들어있는 USceneComponent를 상속하도록 하였다.

1인칭 Camera Component에 Attach하여 플레이어 기준 Location, Rotation을 받아올 수 있도록 하였다. 

 

Trace Channel Collision Setting

 

레벨에 배치된 모든 Actor를 잡을 수 있다면 벽과 바닥도 플레이어가 잡을 수 있게 된다.

우리가 원하는 기능은 특정 물체만 잡을 수 있도록 하는 것이기 때문에 이를 위해 Default Response가 Ignore인 Trace Channel이 필요하다.

이후 잡을 수 있게 만들고 싶은 Actor에만 Grabber Channel에 대한 Response를 Block으로 변경하여 Sweep 할 때 Hit이 이루어지도록 설정하자.

 

 

Grab & Release  Implementation

Grab, Release하는 과정을 순서대로 나열하면 아래와 같다.

1. 마우스 클릭 이벤트를 받아서 플레이어의 시점을 기준으로 전방으로 일정 거리 Sweep 하여 충돌이 일어난 Actor가 있는지 확인

2. 있다면 Physics Handle에 해당 Actor를 부착하기

3. 마우스 클릭이 Release되었 다면 Physics Handle에서 Actor 제거 

 

 

잡을 수 있는 물체가 사거리 내에 있는가를 판단하기 위한 함수 GetGrabbableInReach를 정의하였다.

Grabber의 위치를 시작점, 시작점에서 플레이어의 전방 unit vector * 사거리를 더해 도착점으로 설정하고 구 모양으로 Sweep하여 Collision을 확인하는 함수이다.

만약 충돌이 일어났다면 true를 반환하고 OutHitResult에 정보가 기록된다.

 

bool UGrabber::GetGrabbableInReach(FHitResult& OutHitResult) const
{
	FVector Start = GetComponentLocation();
	FVector End = Start + GetForwardVector() * MaxGrabDistance;

	FCollisionShape Sphere = FCollisionShape::MakeSphere(GrabRadius);
	FHitResult HitResult;

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

}

 

 

GetGrabbableInReach로 충돌 Actor를 가져와 PhysicsHandle에 Actor를 Attach하기 까지 모두 담당하는 Grab 함수이다.

 

Physics Handle이 없으면 Grab을 수행하지 않는다.

앞서 설명한 GetGrabbableInReach를 사용하여 충돌한 Component(Shape Component)와 Actor의 정보를 가져온 후, Simulate Physics을 true로 바꾸고, Physical Material 설정에 따라 Sleep 상태일 경우를 대비해서 WakeAllRigidBodies를 추가하였다.

다음으로 Physics Handle에 부착시키는 GrabComponentAtLocationWithRotation 함수를 사용하였다.

Location은 충돌이 일어난 Point, Rotation은 Grabber의 Rotation을 사용하였다. 

 

void UGrabber::Grab()
{
	UPhysicsHandleComponent* PhysicsHandle = GetPhysicsHandle();
	if (PhysicsHandle == nullptr)
		return;
	
	FHitResult HitResult;
	bool HasHit = GetGrabbableInReach(HitResult);

	if (HasHit)
	{
		UPrimitiveComponent* HitComponent = HitResult.GetComponent();
		HitComponent->SetSimulatePhysics(true);
		HitComponent->WakeAllRigidBodies();
		PhysicsHandle->GrabComponentAtLocationWithRotation(
			HitComponent,
			NAME_None,
			HitResult.ImpactPoint,
			GetComponentRotation()
		);
	}

}

 

 

Release 함수는 Physics Handle에서 GetGrabbedComponent로 잡고있는 Component가 있다면 가져오고 ReleaseComponent를 사용하여 놓아주는 코드로 구성되어 있다.

 

이동하거나 방향을 바꿔도 플레이어의 Location, Rotation을 따라오는 Grab기능이 잘 작동하는 것을 아래 사진에서 확인할 수 있다.

 

 

Reference

[0] https://dev.epicgames.com/documentation/en-us/unreal-engine/BlueprintAPI/Physics/Components/PhysicsHandle

 

Physics Handle | Unreal Engine 5.4 Documentation | Epic Developer Community

Physics Handle

dev.epicgames.com

[1] Udemy - Unreal Engine 5 C++ Developer: Learn C++ & Make Video Games