@DanM
I figured it out! I’m so ridiculously happy right now haha. I was very much on the right track with the debug lines, and finding some people solutions with blueprints took me the rest of the way. To solve this I needed to take the location vector of my ProjectileSpawnPoint, and subtract the Hit.ImpactPoint location vector. Store this in a new FVector, and then use that FVector’s rotation in my SpawnActor for Projectile.
This means I also no longer need the “FireInDirection” code.
here is code for all four parts (header files and c++ files)
ProjectileBase header:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/SphereComponent.h"
#include "GameFramework/Actor.h"
#include "ProjectileBase.generated.h"
class UProjectileMovementComponent;
UCLASS()
class SIMPLESHOOTER_API AProjectileBase : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AProjectileBase();
private:
// COMPONENTS
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UProjectileMovementComponent* ProjectileMovement;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* ProjectileMesh;
USphereComponent* CollisionComponent;
UPROPERTY(EditDefaultsOnly, Category = "Components")
TSubclassOf<UDamageType> DamageType;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UParticleSystemComponent* ParticleTrail;
UPROPERTY(EditAnywhere, Category = "Components")
UParticleSystem* HitParticle;
UPROPERTY(EditAnywhere, Category = "Effects")
USoundBase* HitSound;
// VARIABLES
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement", meta = (AllowPrivateAccess = "true"))
float MovementSpeed = 3000.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage", meta = (AllowPrivateAccess = "true"))
float Damage = 50.f;
// FUNCTIONS
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,FVector NormalImpulse, const FHitResult& Hit);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void PostActorCreated() override;
};
Projectile C++:
// Fill out your copyright notice in the Description page of Project Settings.
#include "ProjectileBase.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "particles/ParticleSystemComponent.h"
#include "DrawDebugHelpers.h"
// Sets default values
AProjectileBase::AProjectileBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Projectile Mesh"));
ProjectileMesh->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit);
RootComponent = ProjectileMesh;
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Projectile Movement"));
ParticleTrail = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Particle Trail"));
ParticleTrail->SetupAttachment(RootComponent);
}
void AProjectileBase::PostActorCreated()
{
ProjectileMovement->Activate();
ProjectileMovement->InitialSpeed = MovementSpeed;
ProjectileMovement->MaxSpeed = MovementSpeed;
InitialLifeSpan = 5.f;
}
void AProjectileBase::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
FVector NormalImpulse, const FHitResult& Hit)
{
AActor* MyOwner = GetOwner();
if (!MyOwner)
{
return;
}
if (OtherActor && OtherActor != this && OtherActor != MyOwner)
{
//OtherComp->AddImpulseAtLocation(ProjectileMovement->Velocity * 100.f, Hit.ImpactPoint);
UGameplayStatics::ApplyDamage(OtherActor,Damage,MyOwner->GetInstigatorController(),this,DamageType);
UGameplayStatics::SpawnEmitterAtLocation(this, HitParticle, GetActorLocation());
UGameplayStatics::PlaySoundAtLocation(this, HitSound,GetActorLocation());
Destroy();
}
}
// Called when the game starts or when spawned
void AProjectileBase::BeginPlay()
{
Super::BeginPlay();
}
Gun header:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ProjectileBase.h"
#include "Actors/FPSProjectile.h"
#include "GameFramework/Actor.h"
#include "Gun.generated.h"
class USceneComponent;
UCLASS()
class SIMPLESHOOTER_API AGun : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AGun();
void PullTrigger();
int GetAmmo();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
// COMPONENTS
UPROPERTY(VisibleAnywhere, Category = "Components")
USceneComponent* Root;
UPROPERTY(VisibleAnywhere, Category = "Components")
USkeletalMeshComponent* Mesh;
UPROPERTY(EditAnywhere, Category = "Components")
UParticleSystem* EmptyMuzzleFlash;
UPROPERTY(EditAnywhere, Category = "Components")
UParticleSystem* MuzzleFlash;
UPROPERTY(EditAnywhere, Category = "Components")
USoundBase* MuzzleSound;
UPROPERTY(EditAnywhere, Category = "Components")
USoundBase* EmtpySound;
UPROPERTY(EditAnywhere, Category = "Components")
UParticleSystem* HitParticle;
UPROPERTY(EditAnywhere, Category = "Components")
USoundBase* HitSound;
UPROPERTY(EditAnywhere, Category = "Components")
USceneComponent* ProjectileSpawnPoint;
// VARIABLES
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile Type", meta = (AllowPrivateAccess = "true"))
TSubclassOf<AProjectileBase> ProjectileClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile Type", meta = (AllowPrivateAccess = "true"))
TSubclassOf<AFPSProjectile> FPSProjectileClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay", meta = (AllowPrivateAccess = "true"))
FVector MuzzleOffset;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay", meta = (AllowPrivateAccess = "true"))
float MaxRange = 10000.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay", meta = (AllowPrivateAccess = "true"))
float Damage = 10.f;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Gameplay", meta = (AllowPrivateAccess = "true"))
int Ammo = 0;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Gameplay", meta = (AllowPrivateAccess = "true"))
int MaxAmmo = 25;
bool GunTrace(FHitResult& Hit, FVector& ShotDirection);
AController* GetOwnerController() const;
};
Gun C++:
// Fill out your copyright notice in the Description page of Project Settings.
#include "Gun.h"
#include "Components/SkeletalMeshComponent.h"
#include "Kismet/GameplayStatics.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) // ensures the projectile class exists (is selected) before firing
{
FHitResult Hit; // needed for GunTrace()
FVector ShotDirection; // needed for GunTrace()
bool bSuccess = GunTrace(Hit, ShotDirection);
if (bSuccess) // if GunTrace() hits something
{
// Get spawn location for projectile (just ahead of weapon barrel)
FVector Location = ProjectileSpawnPoint->GetComponentLocation();
// Get the difference of vectors between GunTrace(FHitResult& Hit) Impact Point and ProjectileSpawnPoint ComponentLocation
FVector Difference = Hit.ImpactPoint - Location;
FActorSpawnParameters Params; // set params to ignore the owner and instigator (for collisions)
Params.Owner = this;
Params.Instigator = GetInstigator();
// fires the newly crated projectile from the spawn point to the space where the GunTrace() trace has hit a target
AProjectileBase* FiredProjectile = GetWorld()->SpawnActor<AProjectileBase>(ProjectileClass, Location, Difference.Rotation(), Params );
FiredProjectile->SetOwner(this);
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* 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);
}
Oh man, this was a wild ride. Thanks for all your help, @DanM . Really appreciated you getting back tome so quickly and bouncing ideas off with me. If it wasn’t for your help I would not have solved this!