Fire Projectile to Crosshairs in Third person c++

Hey there!

So I’m new to Unreal Engine, and I’m currently trying to make a TPS (third-person shooter) tech demo. I managed to get projectiles firing from my weapon, however the problem is they don’t currently fire where my crosshair is aiming, They’re just a little off.

Red: Where my crosshair Hits
Blue: where my projectile hits
Green is where I want my projectile to hit.

For the player controlled character, they can technically compensate by aiming over more. Problem is for AI it shoots to the right of my pawn:

Now, in my code using the line trace (from the camera, and from the AI controller) and using it’s impact point works for drawing the green line. IE: This is where I want the projectile to launch towads:

// Fill out your copyright notice in the Description page of Project Settings.


#include "Gun.h"
#include "Components/SkeletalMeshComponent.h"
#include "Kismet/GameplayStatics.h"
#include "DrawDebugHelpers.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetMathLibrary.h"


// Sets default values
AGun::AGun()
{
 	// 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;
	
	Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
	SetRootComponent(Root);

	Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh"));
	Mesh->SetupAttachment(Root);

	ProjectileSpawnPoint = CreateDefaultSubobject<USceneComponent>(TEXT("Projectile Spawn Point"));
	ProjectileSpawnPoint->SetupAttachment(Mesh);

	Ammo = MaxAmmo;
}

void AGun::PullTrigger()
{
	if (Ammo > 0)
	{
		UGameplayStatics::SpawnEmitterAttached(MuzzleFlash, Mesh, TEXT("MuzzleFlashSocket"));
		UGameplayStatics::SpawnSoundAttached(MuzzleSound, Mesh, TEXT("MuzzleFlashSocket"));

		if (ProjectileClass)
		{
			FHitResult Hit;
			FVector ShotDirection;
			bool bSuccess = GunTrace(Hit, ShotDirection);
			if (bSuccess)
			{
				FVector Location = ProjectileSpawnPoint->GetComponentLocation();
				FRotator Rotation = ProjectileSpawnPoint->GetComponentRotation() + ShotDirection.Rotation(); 
				//FRotator Rotation = ProjectileSpawnPoint->GetComponentRotation();
				//FVector Direction = Location - ShotDirection;
				FActorSpawnParameters Params;
				Params.Owner = this;
				Params.Instigator = GetInstigator();
				AProjectileBase* Projectile = GetWorld()->SpawnActor<AProjectileBase>(ProjectileClass, Location,Rotation,Params );
				if (Projectile)
				{
					
					Projectile->FireInDirection(-ShotDirection);
					//Projectile->FireInDirection(Hit.Location);
				}
				Ammo -= 1;
			}
		}
	}
	else
	{
		UGameplayStatics::SpawnSoundAttached(EmtpySound, Mesh, TEXT("MuzzleFlashSocet"));
		UGameplayStatics::SpawnEmitterAttached(EmptyMuzzleFlash, Mesh, TEXT("MuzzleFlashSocket"));
	}
}

int AGun::GetAmmo()
{
	return Ammo;
}

// Called when the game starts or when spawned
void AGun::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void AGun::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	AController *OwnerController = GetOwnerController();
	//if (!OwnerController) return false;
	
	FVector PlayerLocation;
	FRotator PlayerRotation;
	OwnerController->GetPlayerViewPoint(PlayerLocation,PlayerRotation);

	FVector End = PlayerLocation + PlayerRotation.Vector() * MaxRange;
	DrawDebugLine(
		GetWorld(),
		PlayerLocation,
		End,
		FColor::Red,
		false,
		0,
		0,
		5.f
	);

	FHitResult WorldHit;

	FVector ProjVec = ProjectileSpawnPoint->GetComponentLocation();
	FRotator ProjRot = ProjectileSpawnPoint->GetComponentRotation();
	FVector EndProj = ProjVec + ProjRot.Vector() * MaxRange;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this);
	Params.AddIgnoredActor(GetOwner());
	GetWorld()->LineTraceSingleByChannel(WorldHit, PlayerLocation, End, ECollisionChannel::ECC_GameTraceChannel1, Params);
	

	DrawDebugLine(
		GetWorld(),
		ProjVec,
		WorldHit.ImpactPoint,
		FColor::Green,
		false,
		0,
		0,
		5.f
	);

	
	DrawDebugLine(
		GetWorld(),
		ProjVec,
		End,
		FColor::Blue,
		false,
		0,
		0,
		5.f
	);

	
}

AController* AGun::GetOwnerController() const
{
	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	if (OwnerPawn == nullptr) return nullptr;
	return OwnerPawn->GetController();
}

bool AGun::GunTrace(FHitResult& Hit, FVector& ShotDirection)
{
	AController *OwnerController = GetOwnerController();
	if (!OwnerController) return false;
	
	FVector PlayerLocation;
	FRotator PlayerRotation;
	OwnerController->GetPlayerViewPoint(PlayerLocation,PlayerRotation);
	ShotDirection = -PlayerRotation.Vector();
	
	FVector End = PlayerLocation + PlayerRotation.Vector() * MaxRange;
	FCollisionQueryParams Params;
	Params.AddIgnoredActor(this);
	Params.AddIgnoredActor(GetOwner());
	return GetWorld()->LineTraceSingleByChannel(Hit, PlayerLocation, End, ECollisionChannel::ECC_GameTraceChannel1, Params);
}

However, in the section where the projectile is initiated I have to currently use my old firing system(blue line), as when I try to use the Hit.ImpactPoint, Hit.Location etc in the FireInDirection call, the projectile explodes right at the spawn point. The spawn point is ahead of the barrel (where blue and green start).

Code for projectile fire event:

void AProjectileBase::FireInDirection(const FVector& ShootDirection)
{
	ProjectileMovement->Velocity = ShootDirection * ProjectileMovement->InitialSpeed;
}

This has been driving me bonkers for a few days now.

set an arrow component on the muzzle.
First trace a infinite ray from camera origin and direction and get the hit point then calculate the forward vector from muzzle to hit point , convert it to a rotation and set arrow rotation with. then set projectile velocity direction by arrow rotation.
If there is no hit point just use trace end point.
That’s it so easier than you thought.

I ended up doing something similar, in practice. I was overcomplicating it a bit, i ended up taking the from the GetPlayerViewPoint, and subtracting the vector from the ProjectileSpawn point, than I just use the rotation of that as the third argument in SpawnActor, and it works. Also gets rid of the weird "FfireInDirection"method I had that I didn’t need at all