I also went with a struct approach to remove duplicate code.
.h
#pragma once
#include "CoreMinimal.h"
#include "PhysicsEngine/PhysicsHandleComponent.h"
#include "MyGrabberComponent.generated.h"
struct FGrabTrace
{
FVector TraceStart;
FVector TraceEnd;
};
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class BUILDING_ESCAPE_API UMyGrabberComponent : public UActorComponent
{
GENERATED_BODY()
public:
UMyGrabberComponent();
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:
virtual void BeginPlay() override;
virtual void Grab();
virtual void Release();
virtual void InitPhysicsHandle();
virtual void InitBindings();
virtual void DrawTrace(const FGrabTrace GrabTrace) const;
virtual void GetFirstPhysicsBodyInReach(const FGrabTrace& GrabTrace, FHitResult& HitResult) const;
virtual FGrabTrace GetGrabTrace() const;
private:
UPROPERTY(EditAnywhere)
float Reach = 200.f;
UPROPERTY(EditAnywhere)
bool bDebugTrace = false;
UPROPERTY(Transient)
UPhysicsHandleComponent* PhysicsHandle = nullptr;
UPROPERTY(Transient)
UInputComponent* InputComponent = nullptr;
};
.ccp
#include "MyGrabberComponent.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include "DrawDebugHelpers.h"
UMyGrabberComponent::UMyGrabberComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UMyGrabberComponent::BeginPlay()
{
Super::BeginPlay();
InitPhysicsHandle();
InitBindings();
}
void UMyGrabberComponent::InitPhysicsHandle()
{
PhysicsHandle = GetOwner()->FindComponentByClass<UPhysicsHandleComponent>();
if (!IsValid(PhysicsHandle))
{
UE_LOG(LogTemp, Error, TEXT("MyGrabberComponent requires a PhysicsHandleComponent to be attached to %s"), *GetOwner()->GetName());
}
}
void UMyGrabberComponent::InitBindings()
{
UInputComponent* InputComp = GetOwner()->FindComponentByClass<UInputComponent>();
if (IsValid(PhysicsHandle))
{
InputComp->BindAction("Grab", EInputEvent::IE_Pressed, this, &UMyGrabberComponent::Grab);
InputComp->BindAction("Grab", EInputEvent::IE_Released, this, &UMyGrabberComponent::Release);
}
}
FGrabTrace UMyGrabberComponent::GetGrabTrace() const
{
FRotator PlayerViewPointRotation;
FGrabTrace GrabTrace;
GetWorld()->GetFirstPlayerController()->GetPlayerViewPoint(GrabTrace.TraceStart, PlayerViewPointRotation);
GrabTrace.TraceEnd = GrabTrace.TraceStart + (PlayerViewPointRotation.Vector() * Reach);
return GrabTrace;
}
void UMyGrabberComponent::Grab()
{
FHitResult ActorHit;
const FGrabTrace GrabTrace = GetGrabTrace();
GetFirstPhysicsBodyInReach(GrabTrace, ActorHit);
UPrimitiveComponent* ComponentToGrab = ActorHit.GetComponent();
if (ActorHit.GetActor())
{
PhysicsHandle->GrabComponentAtLocation(
ComponentToGrab,
NAME_None,
GrabTrace.TraceEnd);
}
}
void UMyGrabberComponent::Release()
{
PhysicsHandle->ReleaseComponent();
}
void UMyGrabberComponent::DrawTrace(const FGrabTrace GrabTrace) const
{
DrawDebugLine(GetWorld(),
GrabTrace.TraceStart,
GrabTrace.TraceEnd,
FColor{0, 255, 0},
false, -1, 0, 3.0f
);
}
void UMyGrabberComponent::GetFirstPhysicsBodyInReach(const FGrabTrace& GrabTrace, FHitResult& HitResult) const
{
if (bDebugTrace)
{
DrawTrace(GrabTrace);
}
const FCollisionQueryParams TraceParams{FName(TEXT("")), false, GetOwner()};
GetWorld()->LineTraceSingleByObjectType(
HitResult,
GrabTrace.TraceStart,
GrabTrace.TraceEnd,
FCollisionObjectQueryParams(ECollisionChannel::ECC_PhysicsBody),
TraceParams
);
}
void UMyGrabberComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (IsValid(PhysicsHandle) && PhysicsHandle->GrabbedComponent)
{
const FGrabTrace GrabTrace = GetGrabTrace();
PhysicsHandle->SetTargetLocation(GrabTrace.TraceEnd);
}
}