Using Line Traces in Unreal Engine 5 with C++

Line traces can be great tools when needing to get information from hit objects or to understand other relational data through a raycast.

Unreal Engine 5 Quinn standing in front of C++ line trace actors

Software Versions: Unreal Engine 5.5.0 | Rider 2024.3

Project Name: MyProject

Line traces can be extremely helpful in game development when wanting to get hit data. Particularly, shooting out a raycast will return copious amounts of data from the hit object, including, but not limited to, object/actor properties, normal points, direction, bones, etc. There a few different ways to perform line traces in UE5, each with their own benefits, in this example we'll do four single line traces updating our HitResult when possible.

Start by creating a new Unreal C++ actor class and call itMyActorLineTrace. The header file has a very basic configuration, the scene and mesh components are for decoration, the SingleLineTrace is where we'll trigger our line trace functions, and then I added an int32 variable named TestFunc to simply assist with a very basic switch statement for the four variations of line traces we'll perform.

MyActorLineTrace.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActorLineTrace.generated.h"

UCLASS()
class MYPROJECT_API AMyActorLineTrace : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AMyActorLineTrace();
	
	UPROPERTY(EditAnywhere)
	USceneComponent* Root;

	UPROPERTY(EditAnywhere)
	UStaticMeshComponent* StaticMesh;

	UPROPERTY(EditAnywhere)
	int32 TestFunc = 0;

	UFUNCTION(BlueprintCallable)
	void SingleLineTrace();
};

Inside the .cpp file begin by first including the header files we'll need (DrawDebugHelpers and KismetSystemLibrary) and then complete the constructor with a standard basic setup.

#include "MyActorLineTrace.h"
#include "DrawDebugHelpers.h"
#include "Kismet/KismetSystemLibrary.h"

// Sets default values
AMyActorLineTrace::AMyActorLineTrace()
{
	Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
	Root->bVisualizeComponent = true;
	RootComponent = Root;
	
	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMesh->SetupAttachment(Root);
}

I created a quick switch statement to illustrate the different techniques of running a line trace. All the switch statement does is switch on TestFunc then performs the following function.

All line trace functions share similar parameters. Where is the raycast going to start? Where is it going to end? What color should it be, etc..? At the top of the function let's declare our variables.

void AMyActorLineTrace::SingleLineTrace()
{
	UWorld* const World = GetWorld();
	FVector Start = GetActorLocation();
	FVector End = (GetActorForwardVector() * 2000.f) + Start;
	TArray<AActor*> ActorsToIgnore;
	ActorsToIgnore.Add(this);
	FHitResult OutHit;

	bool bHit = false;
...

Except for the debug line trace, line traces return a bool indicating if it hit something, if it did hit something then our FHitResult named OutHit was updated with all the relative information. In this example we're using two single line trace functions from the UKismetSystemLibrary, LineTraceSingle and LineTraceSingleByProfile, they only differ by their trace channel.

Next, we can call a line trace directly from our World object. Running LineTraceSingleByChannel operates very similarly as the others except it takes a value from the ECollisionChannel enum for its trace channel. In this example we're using ECC_Visibility. LineTraceSingleByChannel does not actually draw a visual indicator line like the previous functions. Finally, we use the debug helpers to draw a line using DrawDebugLine, this method does visually draw a line in the world, but it does not return a bool nor does it update our FHitResult.

MyActorLineTrace.h

#include "MyActorLineTrace.h"
#include "DrawDebugHelpers.h"
#include "Kismet/KismetSystemLibrary.h"

// Sets default values
AMyActorLineTrace::AMyActorLineTrace()
{
	Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
	Root->bVisualizeComponent = true;
	RootComponent = Root;
	
	StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMesh->SetupAttachment(Root);
}

void AMyActorLineTrace::SingleLineTrace()
{
	UWorld* const World = GetWorld();
	FVector Start = GetActorLocation();
	FVector End = (GetActorForwardVector() * 2000.f) + Start;
	TArray<AActor*> ActorsToIgnore;
	ActorsToIgnore.Add(this);
	FHitResult OutHit;

	bool bHit = false;

	switch (TestFunc)
	{
		case 0:
			bHit = UKismetSystemLibrary::LineTraceSingle(
				World,
				Start,
				End,
				ETraceTypeQuery::TraceTypeQuery1,
				false,
				ActorsToIgnore,
				EDrawDebugTrace::Persistent,
				OutHit,
				true
				);
		
			break;
		case 1:
			bHit = UKismetSystemLibrary::LineTraceSingleByProfile(
				World,
				Start,
				End,
				FName("BlockAll"),
				false,
				ActorsToIgnore,
				EDrawDebugTrace::Persistent,
				OutHit,
				true
				);
		
			break;
		case 2:
			bHit = World->LineTraceSingleByChannel(
				OutHit,
				Start,
				End,
				ECC_Visibility
				);
		
			break;

		case 3:
			DrawDebugLine(World, Start, End, FColor::Red, true);

			break;
		default:
			bHit = false;
	}
	
	if (bHit)
	{
		UE_LOG(LogTemp, Warning, TEXT("Single Trace Object Name: %s"), *OutHit.HitObjectHandle.GetName());

		FString ActorDisplayName = UKismetSystemLibrary::GetDisplayName(OutHit.GetActor());
		UE_LOG(LogTemp, Warning, TEXT("Profile Trace OutHit Display Name: %s"), *ActorDisplayName);
	}
}

I'm looking forward to using line traces more in the future.

Loading...