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.
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.
Harrison McGuire