
특정 액터를 잡아 이동시킬 수 있는 간단한 Grab 기능을 구현하기
- 트레이스 채널 추가 (Grabber[이름], Ignore)
- 그랩으로 움직일 액터 설정
Grabber[Block] 및 오버랩 이벤트 생성 활성화
Simulate Physics 활성화
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"));
}

- 그랩 최대 거리를 저장하는 변수 필요
- 그랩 후 물체를 잡고 있을 거리를 저장하는 변수 필요
- 충돌체(구체)의 반지름을 저장하는 변수 필요 (충돌 판정)
- 액션 매핑을 위한 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;
};
// 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
);
}