You would return an empty optional (default constructed optional). Here’s the function (though the functions merged because having this as 3 functions always bothered me)
TOptional<FVector> ATankPlayerController::GetSightRayHitLocation() const
{
// Find the crosshair position in pixel coordinates
int32 ViewportSizeX, ViewportSizeY;
GetViewportSize(ViewportSizeX, ViewportSizeY);
FVector2D ScreenLocation(ViewportSizeX * CrosshairXLocation, ViewportSizeY * CrosshairYLocation);
FVector LookDirection;
FVector StartLocation;
// "De-project" the screen position of the crosshair to a world direction
bool Projected = DeprojectScreenPositionToWorld(
ScreenLocation.X,
ScreenLocation.Y,
StartLocation,
LookDirection
);
if (Projected)
{
FHitResult HitResult;
FVector EndLocation = StartLocation + (LookDirection * LineTraceRange);
if (GetWorld()->LineTraceSingleByChannel(
HitResult,
StartLocation,
EndLocation,
ECollisionChannel::ECC_Camera)
)
{
return HitResult.Location;
}
}
return {};
}
Then at the call site:
auto HitLocation = GetSightRayHitLocation();// Has "side-effect", is going to line trace
if (HitLocation.IsSet())
{
AimingComponent->AimAt(HitLocation.GetValue());
}