LineTrace in the direction of movement

I have a top down camera that is inside a pawn that I use to look into the game world. I want to trace from the camera to the ground, in the direction the camera is in moving.
For example when I press the Right Arrow on the keyboard, the camera will start moving right, and the LineEnd of the trace should hit the edge of the right side of the screen.

How would I go about this?

Hey,

You can use the input keys axis maping to execute a function and in it deproject screen position to world.

For example: press right (D key, X+ direction); you know it will be horizontal and Axis values will be positive. So using that you can get screen position (X * 1.0, Y* 0.5) to get mid point on right edge of viewport and trace from there.

Like so:


TraceScreenEdge

Hope it helps.

1 Like

Thanks for taking the time to reply. That is exactly what I am looking for. I guess I am having trouble understanding the logic, as it does not have the desired affect. I have this:

if (MovementVector.X || MovementVector.Y != 0.0f)
		{
			FVector2D ViewportSize;
			World->GetGameViewport()->GetViewportSize(ViewportSize);

			FVector2D ScreenPosition;
			if (MovementVector.Y > 0.0f)
			{
				ScreenPosition = FVector2D(ViewportSize.X * 0.5f, ViewportSize.Y * 0.0f);
			}
			if (MovementVector.Y < 0.0f)
			{
				ScreenPosition = FVector2D(ViewportSize.X * 1.0f, ViewportSize.Y * 0.5f);
			}
			
			FVector WorldLocation;
			FVector WorldDirection;

			const APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
			PlayerController->DeprojectScreenPositionToWorld(ScreenPosition.X, ScreenPosition.Y, WorldLocation, WorldDirection);

			const FVector TraceStart = WorldLocation;
			const FVector TraceEnd = TraceStart + WorldDirection * 1000.0f;

			DrawDebugLine(
				GetWorld(),
				TraceStart,
				TraceEnd,
				FColor::Red,
				false,
				-1,
				0
			);
}

Can you see anything obviously wrong with this compared to yours?

Don’t how MovementVector get its value so can’t say if the conditions will work but…

These two should be if... else, because after checking != 0.f, if it is not > then it is <.


For reference here is the shared BP in cpp:

void AMyCharacter::TraceViewportBorder(const float& AxisValue, const bool bIsVertical)
{
	// Do nothing if no value.
	if (AxisValue == 0.f) return;

	const bool bIsPositive = (AxisValue > 0) ? 1 : 0 ;

	FVector2D ViewportPosition; 
	FVector2D ViewportSize; 
	World->GetGameViewport()->GetViewportSize(ViewportSize);

	if (bIsVertical)
	{
		// Screen position for top and bottom.
		ViewportPosition = FVector2D(ViewportSize.X * 0.5, ViewportSize.Y * (float)!bIsPositive);
	} 
	else
	{
		// Screen position for left and right.
		ViewportPosition = FVector2D((float)bIsPositive * ViewportSize.X, ViewportSize.Y * 0.5);
	}

	FVector TraceStart;
	FVector WorldDirection;
	const APlayerController* APC = World->GetFirstPlayerController();

	APC->DeprojectScreenPositionToWorld(ViewportPosition.X, ViewportPosition.Y, TraceStart, WorldDirection);
	
	FHitResult HitResult;
	const FVector TraceEnd = TraceStart + WorldDirection * 10000.f;
	FCollisionQueryParams QueryParams;
	QueryParams.AddIgnoredActor(this);

	World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility, QueryParams);
	
	// Return if false.
	if (!HitResult.bBlockingHit) return;

	// #include "DrawDebugHelpers.h"
	DrawDebugSphere(World, HitResult.ImpactPoint, 50.f, 4, FColor::Green, false, 2.f);
}

Thank you for the reply. That code is doing the same as the above was.

So in the Pawn input function, MoveCamera I pass the MovementVector to the TraceFromCamera function (So TraceFromCamera only fires when a key is pressed).

// Get Axis value.
const FVector MovementVector = Value.Get<FVector>();

if (MovementVector.X != 0 || MovementVector.Y != 0)
{
 TraceFromCamera(MovementVector);
}

Which is giving me the Axis value. (Right (D) X: 1.0, Up (W) Y: 1.0) etc.

In my TraceFromCamera function I have tried to adapt your code above to:

UWorld* World = GetWorld();
if (World != nullptr)
{
 	FVector2D ViewportPosition; 
	FVector2D ViewportSize; 
	World->GetGameViewport()->GetViewportSize(ViewportSize);
   
        // Check if vertical movement.
	if (MovementVector.Y > 0)
	{
		ViewportPosition = FVector2D(ViewportSize.X * 0.5, ViewportSize.Y); // Removed * (float)!bIsPositive) as it would of been 0?
	}
	else
	{
		ViewportPosition = FVector2D(1.0f * ViewportSize.X, ViewportSize.Y * 0.5); // Added * 1.0f, as bIsPositive would be 1.0?
	}

	FVector TraceStart;
	FVector WorldDirection;
	const APlayerController* APC = World->GetFirstPlayerController();

	APC->DeprojectScreenPositionToWorld(ViewportPosition.X, ViewportPosition.Y, TraceStart, WorldDirection);

	const FVector TraceEnd = TraceStart + WorldDirection * 10000.f;

	DrawDebugLine(
		GetWorld(),
		TraceStart,
		TraceEnd,
		FColor::Red,
		false,
		-1,
		0
	);
}

I think I am mistaking your bIsVertical bool with Y Axis movement?

If I set

ViewportPosition = FVector2D(ViewportSize.X * 0.5, ViewportSize.Y); // Removed * (float)!bIsPositive) as it would of been 0?

Then it does not give any DebugLine that I can see.

But with

ViewportPosition = FVector2D(1.0f * ViewportSize.X, ViewportSize.Y * 0.5); // Added * 1.0f, as bIsPositive would be 1.0?

It keeps the line trace to the Rightside-Centre point when I press D. And top right-ish with I press down. The startline does not appear to be in the middle when going down, only when moving right.

The issue I see here is that if (MovementVector.X != 0 || MovementVector.Y != 0) will be true on any movement key press and that is fine, but the else of if (MovementVector.Y > 0) will run every time because Y <= 0.f will be true even if only X != 0.

The purpose of bIsVertical is to separate on true W (up) and S (down) inputs and on false A (left) and D (right) inputs to avoid having to write extra conditions.

You could do something like const bool bIsVertical = (MovementVector.Y != 0.f) ? true : false; to know if it was AD (false) or WS (true).

  • True: either top middle or bottom middle, so MovementVector.X will always be * 0.5 and MovementVector.Y will either be 0 or 100% the screen height.
  • False: either left middle or right middle, so MovementVector.Y will always be * 0.5 and MovementVector.X will either be 0 or 100% the screen width.

The next condition you need to solve is when to know if its top or bottom, left or right. For this I used const bool bIsPositive = (AxisValue > 0.f) ? 1 : 0 ; that gives the result of W = 0 (false) / S = 1 (true) and A = 0 (false) / D = 1 (true).

The end result should be:
— For top/bottom screen position:

ViewportPosition = FVector2D(ViewportSize.X * 0.5f, ViewportSize.Y * (float)!bIsPositive);
  • bIsVertical = true, !bIsPositive = true → S (down) input, trace from bottom middle.
ViewportPosition = FVector2D(ViewportSize.X * 0.5f, ViewportSize.Y * 1.f;
  • bIsVertical = true, !bIsPositive = false → W (up) input, trace from top middle.
ViewportPosition = FVector2D(ViewportSize.X * 0.5f, ViewportSize.Y * 0.f;

The bool is conveniente because we can invert it and get the desired result for both the horizontal and vertical operations.

— For left/right screen position:

ViewportPosition = FVector2D((float)bIsPositive * ViewportSize.X, ViewportSize.Y * 0.5f);
  • bIsVertical = false, bIsPositive = true → D (right) input, trace from right middle.
ViewportPosition = FVector2D(1.f * ViewportSize.X, ViewportSize.Y * 0.5f);
  • bIsVertical = false, bIsPositive = false-> A (left) input, trace from left middle.
ViewportPosition = FVector2D(0.f * ViewportSize.X, ViewportSize.Y * 0.5f);

Hope it makes sense.

1 Like

Thank you for taking the time to explain it to me, it was very helpful the way you broke it down.

I have implemented the above and it works as expected! :+1:

2 Likes