Download

What collision channel would I use for a Use action?

So a while ago in Blueprint, I made a pretty cool system where you could implement an interface on a blueprint which gave it the ability to have a use action. Now I’ve moved over to C++ and it’s a little more complicated than I did in Blueprint, though simpler in nature I guess…I am brand new to C++ in Unreal so please bear with me if you spot something that makes you want to pull hairs out of your head! I’m also very much open to suggestions on how to perhaps do this better?

So here is my setup:

I have a UseComponent which got an OnUse event on it. Just a public function that can be fired from whatever class is getting the component itself. I attach this component to whatever object I want to have a use effect, such as pressing a button on a console or pulling a lever on a wall. Both of them can benefit from the same Use component but the OnUse event is implemented different on either. But here is where I run into issues, I can’t figure out how to set up my Trace so that I can look at pretty much any kind of object to look for the Use component (if any).

Here is the simple code from my UseComponent:

UseComponent.h:


#pragma once

#include "Components/ActorComponent.h"
#include "UseComponent.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FUseEvent); // Used to make Blueprint Events

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class BUILDINGESCAPE_API UUseComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UUseComponent();

	UPROPERTY(BlueprintAssignable)
		FUseEvent OnUse;

	// Called when the game starts
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;

	void FireUseEvent();
};

Pretty simple. I got the custom event FUseEvent and then I have the function FireUseEvent();

From UseComponent.cpp



void UUseComponent::FireUseEvent()
{
	OnUse.Broadcast();
}

Pretty simple right?

In my DefaultPlayerController class I have the following code:

DefaultPlayerController.h


#pragma once

#include "Components/ActorComponent.h"
#include "DefaultPlayerController.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class BUILDINGESCAPE_API UDefaultPlayerController : public UActorComponent
{
	GENERATED_BODY()

public:	
	// How far ahead of the player can we reach in cm
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float Reach = 100.f;
	
	// Sets default values for this component's properties
	UDefaultPlayerController();

	// Called when the game starts
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;

private:
	UPhysicsHandleComponent* PhysicsHandle = nullptr;
	UInputComponent* InputComponent = nullptr;

	/*---------------*/
	/* Input Section */
	/*---------------*/

	// Ray-cast and use what's in reach
	void Use();

	// Ray-cast and grab what's in reach
	void Grab();

	// Called when grab key is released
	void ReleaseGrab();

	/*-----------------*/
	/* Helper Functions*/
	/*-----------------*/

	// Find attached physics handle
	void FindPhysicsHandleComponent();

	// Setup (assumed) attached input component
	void SetupInputComponent();

	// Return hit for first physics body in reach
	const FHitResult GetFirstPhysicsBodyInReach();

	// Return hit for first use handle in reach
	const FHitResult GetFirstUseHandleInReach();

	// Find the vector at the start of the players reach
	FVector GetReachLineStart();

	// Find the vector at the end of the players reach
	FVector GetReachLineEnd();
};

The two methods of interest are:

const FHitResult GetFirstUseHandleInReach(); and void Use();. The first is used to actually do the trace to find an object to interact with. The latter is to try and see if that object got a UUseComponent on it and then call the “FireUseEvent()” method so I can control that event through Blueprint like this:

eeaa1bdb83d3b504a002f94d36e17c035eabf30a.png14edb6d820aea8bd6fc309a565b67f370e34f309.png

Now this works great! Except for one detail:

I can only track one type of object through this. At least to my knowledge. How would I check all kinds of objects that could potentially have this component on it?

In my code I do this:

DefaultPlayerController.cpp


void UDefaultPlayerController::Use()
{
	if (!PhysicsHandle->GrabbedComponent)
	{
		FHitResult HitResult = GetFirstUseHandleInReach();
		AActor* ActorHit = HitResult.GetActor();
		if (ActorHit)
		{
			UUseComponent* UseComp = ActorHit->FindComponentByClass<UUseComponent>();
			if (UseComp)
			{
				UseComp->FireUseEvent();
			}
		}
	}
}

const FHitResult UDefaultPlayerController::GetFirstUseHandleInReach()
{
	/// Line-trace (AKA ray-cast) out to reach
	FHitResult HitResult;
	// Setup Query parameters
	FCollisionQueryParams TraceParameters(FName(TEXT("")), false, GetOwner());

	GetWorld()->LineTraceSingleByObjectType(OUT HitResult, GetReachLineStart(), GetReachLineEnd(), FCollisionObjectQueryParams(ECollisionChannel::ECC_PhysicsBody), TraceParameters);

	return HitResult;
}

This is the line I need help with:


GetWorld()->LineTraceSingleByObjectType(OUT HitResult, GetReachLineStart(), GetReachLineEnd(), FCollisionObjectQueryParams(ECollisionChannel::ECC_PhysicsBody), TraceParameters);

Here I only look for PhysicsBodies. What if I also wanted to look for WorldDynamic, and WorldStatic?

Hi Vipar.

You could trace by channel instead. I use this in my code:



static FName LookTraceIdent = FName(TEXT("LookTrace"));
FCollisionQueryParams TraceParams(LookTraceIdent, true, this);
TraceParams.bTraceAsyncScene = true;

GetWorld()->LineTraceSingleByChannel(HitData, TraceStart, TraceEnd, ECC_Camera, TraceParams);


Then I just look in the HitData for bBlockingHit == true. This detects anything that has Collision:Trace Response:Camera set to Block.

Alternatively, you could use ECC_Visibility (instead of ECC_Camera) and make sure Collision:Trace Response:Visibility is set to Block for anything you want to be able to detect.

Yeah that’s the kind of thing I was looking for. Tracing by channel instead of object type. Thanks!

The solution ended up being changing this line:


GetWorld()->LineTraceSingleByObjectType(OUT HitResult, GetReachLineStart(), GetReachLineEnd(), FCollisionObjectQueryParams(ECollisionChannel::ECC_PhysicsBody), TraceParameters);

for this line:


GetWorld()->LineTraceSingleByChannel(HitResult, GetReachLineStart(), GetReachLineEnd(),ECollisionChannel::ECC_Visibility, TraceParameters);