Extra feature

So this is an aside, I’m trying to get some projectiles to spawn from the gun when pressing fire. I know Daniel mentions that we’re going to do hit/damage based on ray tracing, but i wanted to just try and get the gun producing projectiles. I based most of my code off of code in ToonTanks and some official UE4 tutorials, but my projectiles spawn without Moving.

I’ve developed a projectile class:

Header:

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

#pragma once

#include "CoreMinimal.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();
	void FireInDirection(const FVector& ShootDirection);

private:
	// COMPONENTS
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))	
	UProjectileMovementComponent* ProjectileMovement;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
	UStaticMeshComponent* ProjectileMesh;
	UPROPERTY(EditDefaultsOnly, Category = "Components")
	TSubclassOf<UDamageType> DamageType;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
	UParticleSystemComponent* ParticleTrail;
	// 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;
	
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
};
#include "ProjectileBase.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "particles/ParticleSystemComponent.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"));
	RootComponent = ProjectileMesh;

	ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Projectile Movement"));
	ProjectileMovement->SetUpdatedComponent(ProjectileMesh);

	
	ParticleTrail = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Particle Trail"));
	ParticleTrail->SetupAttachment(RootComponent);


}

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

// Called when the game starts or when spawned
void AProjectileBase::BeginPlay()
{
	Super::BeginPlay();
	ProjectileMovement->InitialSpeed = MovementSpeed;
	ProjectileMovement->MaxSpeed = MovementSpeed;
	InitialLifeSpan = 5.f;
}

Here’s the panes from my BP class:



They spawn statically, and just float in the air like that. I know this isn’t, strictly speaking how the course intended it to go but it’s a feature I just wanted to try and get in. Any idea why the projectiles just don’t move? I’m not sure what I’m missing. In toon tanks this code has the projectile flying out but here it’s just hanging in air.

Anyways, if anyone has any thoughts i’d love to hear them!

I’ve edited your post to use a code block, please use them in the future.

Do you mean projectiles? See here: Toon Tanks: Projectiles Spawn And Fall To The Ground - #7 by DanM

Yes, sorry I meant any idea why the projectiles aren’t moving.

So even following the steps in the link the projectiles do not move. Whether it’s setting the variables in only blue print not C++, whether it’s setting the variables in AProjectileBase::PostActorCreated() override, or whether it’s a combination of those with ->Activate(); in code manually, they spawn at the spawn point, and sit there.

Are you sure the spawn point isn’t too close to the mesh?

Hey Dan,

As far as I can tell it shouldn’t be too close:

Could you put it father out to rule it out?

Hey Dan,

So i moved it out further and that seems to have fixed it. Now I just have to work out making them fire in the correct direction! (as they currently blast out side ways haha). But hey, thanks so much for suggesting that. It’s at least got them moving

So I did manage to get my projectiles firing out in the correct direction, however they do collide with and stop at static objects in the level (walls, etc) but I can’t get them to spawn any emitters or sounds when colliding, and they don’t deal any damage:

// 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();

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* MuzzleFlash;

	UPROPERTY(EditAnywhere, Category = "Components")
	USoundBase* MuzzleSound;

	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;

	bool GunTrace(FHitResult& Hit, FVector& ShotDirection);

	AController* GetOwnerController() const;
};
// 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);
}

void AGun::PullTrigger()
{
	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();
			FActorSpawnParameters Params;
			Params.Owner = this;
			Params.Instigator = GetInstigator();
			AProjectileBase* Projectile = GetWorld()->SpawnActor<AProjectileBase>(ProjectileClass, Location,ShotDirection.Rotation(),Params );
			if (Projectile)
			{
				Projectile->FireInDirection(-ShotDirection);
			}
		}
	}
}

// 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);
}
// 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();
	void FireInDirection(const FVector& ShootDirection);

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;
};


#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::FireInDirection(const FVector& ShootDirection)
{
	ProjectileMovement->Velocity = ShootDirection * ProjectileMovement->InitialSpeed;
}

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();
}

Not sure what I’m mising here, but I’m sure it’s some basic concept.

Is AProjectileBase::OnHit ever called?

	ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit);

The OnComponentHit delegate is bound to this classes OnHit event in the constructor. So it should be called anytime this Component hits anything, but it doesn’t seem to be the case

Did you create a blueprint out of this class before you added that code? That would be the issue of so, recreating the blueprint should fix that.

So recreating the projectile base did make it so that the projectile does damage the enemy and spawns emitters/sounds but as it flies through the air it starts to fall sharply. Even without gravity and physics simulation it falls in an arch from the barrel and falls short of the reticle. Is there something wrong in my trace? This affects the AI mobs as when they aim at my character they all shoot just left of me and can’t hit me.

Hey @DanM ,

I’ve added some debug lines to possibly help get across what’s happening vs what I want to do:

This is what’s currently going on with my projectile launch code at the moment:
Red is where my reticle is drawn (using the HUD instructions from this Course and Section - just a hud page with a reticle drawn in the centre of it).

Blue is where the projectiles are being launched by my current code.

Green is where I want the projectiles to land.

This also explains why my AI is having trouble hitting my player:

As you can see, he’s aimed just to the right of me, instead of at me (it’s a bit hard to follow here obviously)

Gun.cpp:

// 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);
}

So as you can see the code as it is now, spawns the projectile at the spawn point and launches it toward the maximum distance of the line trace from the players view point (Blue Line). When I try to use the OutHit location (from the GunTrace method) as the location to fire the weapon, it doesn’t work. It instead impacts right at the barrel. Almost like it’s “impacting” right as it spawns. This is what is currently commented out (so the code in the code blocks now has the old functionality of the projectile hitting where the blue line is).

I’ve tried Hit.Location, Hit.ImpactPoint and all of them make the projectile explode on initiaion.

However, in the debug line (for the Green line) I am successfully using Hit.ImpactPoint as the location of my line end.

@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!

2 Likes

I didn’t do anything for the final stages so well done on managing that part yourself :slight_smile:

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms