Actor Line Trace
Github Link: https://github.com/Harrison1/unrealcpp/tree/master/ActorLineTrace
For this tutorial we are using the standard first person C++ template with starter content. If you don't know how to add a new actor class to your project, please visit the Add C++ Actor Class post.
Create a new actor, for this example we will call our actor ActorLineTrace
.
We don't have to do anything in the header file. Just in case, below is the header file was created by default.
ActorLineTrace.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ActorLineTrace.generated.h"
UCLASS()
class UNREALCPP_API AActorLineTrace : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AActorLineTrace();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
To help us visualize the line trace we are going to include the DrawDebugHelpers
script. This will allow us to draw a line highlighting the line trace. We are also going to include the ConstructorHelpers.h
to immediately add a mesh to our actor for a visual representation. It's typically preferred to add a mesh in the editor without the help of ConstructoHelpers.h
, but we should continue trying new things in C++.
include DrawDebugHelpers and ConstructorHelpers
#include "ActorLineTrace.h"
// add these scripts to use their functions
#include "ConstructorHelpers.h"
#include "DrawDebugHelpers.h"
We add a cube to the actor by creating a DefaultSubobject
of UStaticMeshComponent
. We then make our new cube the root component of our actor. We programmatically add a mesh to to our actor by calling FObjectFinder
from ConstructorHelpers
. In our FObjectFinder
we provide the path to our mesh. From here we need to check if we successfully got the mesh. If we successfully got the mesh we then set the actors StaticMesh
, RelativeLocation
, and Scale
. Below is the code we put in our actor's init function.
use constructor to add mesh to actor
AActorLineTrace::AActorLineTrace()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// add cube to root
UStaticMeshComponent* Cube = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
Cube->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
if (CubeAsset.Succeeded())
{
Cube->SetStaticMesh(CubeAsset.Object);
Cube->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
Cube->SetWorldScale3D(FVector(1.f));
}
// add another component in the editor to the actor to overlap with the line trace to get the event to fire
}
Next, on our actor's tick function we want to draw the line trace and see if it hits anything. For this example, we will check if it any other objects inside the same actor. Let's create variables for our HitResult
and our StartingPosition
first two variables
void AActorLineTrace::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FHitResult OutHit;
FVector Start = GetActorLocation();
}
The start location is vector which means it has X,Y,Z variables. I want to move the line trace up more towards the center of the mesh and I want to move it away from the mesh to it doesn't collide with itself.
position line trace
void AActorLineTrace::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FHitResult OutHit;
FVector Start = GetActorLocation();
Start.Z += 50.f;
Start.X += 200.f;
}
After that let's get the forward vector of mesh by using GetActorForwardVector()
to make sure the line trace is moving out from the front of the mesh. We will then make and End
variable to tell the line trace where to end. In this example the line trace will start 50 units above and 200 units forward and will end 500 units from it starting point. Also, we create collision param variable for our line trace function.
forward vector, end, and collision params
void AActorLineTrace::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FHitResult OutHit;
FVector Start = GetActorLocation();
Start.Z += 50.f;
Start.X += 200.f;
FVector ForwardVector = GetActorForwardVector();
FVector End = ((ForwardVector * 500.f) + Start);
FCollisionQueryParams CollisionParams;
}
While developing we want to see our line trace. Using the variables we made above we'll use our DrawDebugLine
function to draw a green line. If the line trace touches any other object inside the same actor, we will print a message to the screen.
DrawDebugLine, check hit result, and print message to screen
void AActorLineTrace::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FHitResult OutHit;
FVector Start = GetActorLocation();
Start.Z += 50.f;
Start.X += 200.f;
FVector ForwardVector = GetActorForwardVector();
FVector End = ((ForwardVector * 500.f) + Start);
FCollisionQueryParams CollisionParams;
DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 5);
if(ActorLineTraceSingle(OutHit, Start, End, ECC_WorldStatic, CollisionParams))
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, FString::Printf(TEXT("The Component Being Hit is: %s"), *OutHit.GetComponent()->GetName()));
}
}
Below is the final .cpp
file
ActorLinetrace.cpp
#include "ActorLineTrace.h"
#include "ConstructorHelpers.h"
#include "DrawDebugHelpers.h"
// Sets default values
AActorLineTrace::AActorLineTrace()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// add cube to root
UStaticMeshComponent* Cube = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualRepresentation"));
Cube->SetupAttachment(RootComponent);
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube"));
if (CubeAsset.Succeeded())
{
Cube->SetStaticMesh(CubeAsset.Object);
Cube->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
Cube->SetWorldScale3D(FVector(1.f));
}
// add another component in the editor to the actor to overlap with the line trace to get the event to fire
}
// Called when the game starts or when spawned
void AActorLineTrace::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AActorLineTrace::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FHitResult OutHit;
FVector Start = GetActorLocation();
Start.Z += 50.f;
Start.X += 200.f;
FVector ForwardVector = GetActorForwardVector();
FVector End = ((ForwardVector * 500.f) + Start);
FCollisionQueryParams CollisionParams;
DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 5);
if(ActorLineTraceSingle(OutHit, Start, End, ECC_WorldStatic, CollisionParams))
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, FString::Printf(TEXT("The Component Being Hit is: %s"), *OutHit.GetComponent()->GetName()));
}
}