I do it this way. Works a charm and even works if your cursor doesn’t hit a bit of the world (as its tracing against a virtual plane)
bool ATanksVsZombiesPlayerController::GetMousePositionOnAimingPlane(FVector& IntersectVector) const
{
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player);
bool bHit = false;
if (LocalPlayer && LocalPlayer->ViewportClient)
{
FVector2D MousePosition;
if (LocalPlayer->ViewportClient->GetMousePosition(MousePosition))
{
bHit = GetPlanePositionAtScreenPosition(MousePosition, IntersectVector);
}
}
if (!bHit) //If there was no hit we reset the results. This is redundant but helps Blueprint users
{
IntersectVector = FVector::ZeroVector;
}
return bHit;
}
bool ATanksVsZombiesPlayerController::GetPlanePositionAtScreenPosition(const FVector2D ScreenPosition, FVector& IntersectVector) const
{
// Early out if we clicked on a HUD hitbox
if (GetHUD() != NULL && GetHUD()->GetHitBoxAtCoordinates(ScreenPosition, true))
{
return false;
}
FVector WorldOrigin;
FVector WorldDirection;
if (UGameplayStatics::DeprojectScreenToWorld(this, ScreenPosition, WorldOrigin, WorldDirection) == true)
{
IntersectVector = FMath::LinePlaneIntersection(WorldOrigin, WorldOrigin + WorldDirection * HitResultTraceDistance, GetPawn()->GetActorLocation(), FVector::UpVector);
return true;
}
return false;
}
Then I use it in my tick like this
static const FName NAME_MouseAimingTrace("MouseAimingTrace");
void ATanksVsZombiesPlayerController::AimUsingMouseCursor() const
{
if (!bMouseInput)
return;
// Check we have a proper pawn
ATanksVsZombiesCharacter* Pawn = Cast<ATanksVsZombiesCharacter>(GetPawn());
if (Pawn == nullptr)
return;
// Get the pawn location
FVector PawnLocation = Pawn->GetActorLocation();
// Trace to whats beneath the mouse cursor
FHitResult OutTraceResult;
//GetHitResultUnderCursor(ECC_Pawn, false, OutTraceResult);
FVector IntersectVector;
GetMousePositionOnAimingPlane(IntersectVector);
// Trace down through the aiming plane to see if we hit an actor that we can aim at
FCollisionQueryParams CollisionQueryParams(NAME_MouseAimingTrace, true);
bool bHit = GetWorld()->LineTraceSingleByChannel(OutTraceResult, IntersectVector, IntersectVector - FVector::UpVector * HitResultTraceDistance, ECC_Pawn, CollisionQueryParams);
// If we hit something aim set that as our aim direction, otherwise aim at the point on the plane
FVector Direction = FVector::ZeroVector;
FVector Location = bHit ? OutTraceResult.ImpactPoint : IntersectVector;
if (Location != FVector::ZeroVector)
{
Direction = Location - PawnLocation;
DrawDebugLine(GetWorld(), PawnLocation, Location, FColor(255, 0, 0), false, -1, 0, 10.0f);
if (bHit)
DrawDebugLine(GetWorld(), IntersectVector, OutTraceResult.ImpactPoint, FColor(255, 255, 0), false, -1, 0, 10.0f);
}
// Tell the pawn what its new aim direction is
Pawn->SetAimInputVector(Direction);
}
SetAimInputVector goes through various functions to decide whether to use a joypad direction or the mouse direction, then consume it (reset it to zero vector) then lerp to it over time so things rotate rather than jump instantly. You may want to go back to your own code once you reach FVector Location =, as this is where the mouse cursor fell.