I will show the code for what I did to add the pickups for the health and ammunitions, but I suggest to everyone to try implement it without the solution if they have never done something like this in a game.
For the visuals effects, you can get them for free in the game epics assets store. Search for “Basic Pickups VFX Set (Niagara)”
For the sounds effects, it comes from various assets bought from humblebundle through time ^^^.
So first I created a class which serve as a base, to extend for the pickups.
class AShooterCharacter;
class USoundBase;
UCLASS()
class SIMPLESHOOTER_API APickUpBase : public AActor
{
GENERATED_BODY()
/** Sphere collision component */
UPROPERTY(VisibleDefaultsOnly)
USphereComponent* CollisionComp;
public:
// Sets default values for this actor's properties
APickUpBase();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void InteractWithPlayer(AShooterCharacter* ShooterCharacter);
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditAnywhere)
USoundBase* PickUpSound;
private:
/** Code for when something overlaps this component */
UFUNCTION()
void OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};
#include "PickUpBase.h"
#include "ShooterCharacter.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
class AShooterCharacter;
// Sets default values
APickUpBase::APickUpBase()
{
// 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;
// Use a sphere as a simple collision representation
CollisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
CollisionComp->InitSphereRadius(50.0f);
//CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &APickUpBase::OnBeginOverlap);
}
// Called when the game starts or when spawned
void APickUpBase::BeginPlay()
{
Super::BeginPlay();
CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &APickUpBase::OnBeginOverlap);
}
// Called every frame
void APickUpBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void APickUpBase::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// Checking if it is a First Person Character overlapping
AShooterCharacter* Character = Cast<AShooterCharacter>(OtherActor);
if(Character != nullptr)
{
APlayerController* PlayerController = Cast<APlayerController>(Character->GetController());
if (PlayerController != nullptr)
{
InteractWithPlayer(Character);
}
}
}
void APickUpBase::InteractWithPlayer(AShooterCharacter* ShooterCharacter)
{
if (PickUpSound)
{
UGameplayStatics::SpawnSoundAtLocation(GetWorld(), PickUpSound, ShooterCharacter->GetActorLocation());
}
}
After that I created a child class from APickUpBase => AHealthPickup
#pragma once
#include "CoreMinimal.h"
#include "PickUpBase.h"
#include "HealthPickup.generated.h"
/**
*
*/
UCLASS()
class SIMPLESHOOTER_API AHealthPickup : public APickUpBase
{
GENERATED_BODY()
virtual void InteractWithPlayer(AShooterCharacter* ShooterCharacter) override;
UPROPERTY(EditAnywhere)
int HealthGiven = 50;
};
#include "HealthPickup.h"
#include "ShooterCharacter.h"
void AHealthPickup::InteractWithPlayer(AShooterCharacter* ShooterCharacter)
{
if (ShooterCharacter->IsInjured())
{
Super::InteractWithPlayer(ShooterCharacter);
ShooterCharacter->AddHealth(HealthGiven);
Destroy();
}
}
Then a new child class AmmoPickup. And I made the decision that it will be the gun that has the ammo stored. Honestly it can be the ShooterCharacter himself, it is just as easy.
#pragma once
#include "CoreMinimal.h"
#include "PickUpBase.h"
#include "AmmoPickup.generated.h"
class UTP_PickUpComponent;
class AShooterCharacter;
UCLASS(config=Game)
class SIMPLESHOOTER_API AAmmoPickup : public APickUpBase
{
GENERATED_BODY()
protected:
virtual void InteractWithPlayer(AShooterCharacter* ShooterCharacter) override;
UPROPERTY(EditAnywhere)
int AmmoGiven = 50;
};
#include "AmmoPickup.h"
#include "Gun.h"
#include "ShooterCharacter.h"
#include "Kismet/GameplayStatics.h"
void AAmmoPickup::InteractWithPlayer(AShooterCharacter* ShooterCharacter)
{
AGun* Gun = ShooterCharacter->GetGun();
if (Gun != nullptr && !Gun->IsFullAmmo())
{
Super::InteractWithPlayer(ShooterCharacter);
Gun->AddAmmo(AmmoGiven);
if (PickUpSound)
{
UGameplayStatics::SpawnSoundAtLocation(GetWorld(), PickUpSound, ShooterCharacter->GetActorLocation());
}
Destroy();
}
}
Then I made some change to Gun and ShooterCharacter, with new methods.
For Gun :
void AGun::BeginPlay()
{
Super::BeginPlay();
CurrentAmmo = MaxAmmo;
}
void AGun::PullTrigger()
{
if (CurrentAmmo == 0)
{
if (OutofAmmoSound)
{
UGameplayStatics::SpawnSoundAttached(OutofAmmoSound, Mesh, TEXT("MuzzleFlashSocket"));
}
return;
}
UGameplayStatics::SpawnEmitterAttached(MuzzleFlash, Mesh, TEXT("MuzzleFlashSocket"));
UGameplayStatics::SpawnSoundAttached(MuzzleSound, Mesh, TEXT("MuzzleFlashSocket"));
FHitResult Hit;
FVector ShotDirection;
if (GunTrace(Hit, ShotDirection))
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactEffect, Hit.Location, ShotDirection.Rotation());
UGameplayStatics::SpawnSoundAtLocation(GetWorld(), ImpactSound, Hit.Location);
AActor* HitActor = Hit.GetActor();
if (HitActor != nullptr)
{
FPointDamageEvent DamageEvent(Damage, Hit, ShotDirection, nullptr);
AController* OwnerController = GetOwnerController();
HitActor->TakeDamage(Damage, DamageEvent, OwnerController, this);
}
}
CurrentAmmo--;
}
void AGun::AddAmmo(int NewAmmo)
{
int AmmoTemp = NewAmmo + CurrentAmmo;
CurrentAmmo = FMath::Clamp(AmmoTemp, 0, MaxAmmo);
}
int AGun::GetCurrentAmmo()
{
return CurrentAmmo;
}
int AGun::IsFullAmmo()
{
return CurrentAmmo >= MaxAmmo;
}
inside Gun.h
public:
void AddAmmo(int NewAmmo);
int GetCurrentAmmo();
int IsFullAmmo();
private:
UPROPERTY(EditAnywhere)
USoundBase* OutofAmmoSound;
UPROPERTY(EditAnywhere)
int MaxAmmo = 100;
int CurrentAmmo;
and for ShooterCharacter.cpp
int AShooterCharacter::GetCurrentAmmo() const
{
if (Gun)
{
return Gun->GetCurrentAmmo();
}
return 0;
}
void AShooterCharacter::AddHealth(float NewHealth)
{
Health = FMath::Clamp(NewHealth + Health, 0, MaxHealth);
}
bool AShooterCharacter::IsInjured() const
{
return Health < MaxHealth;
}
inside ShooterCharacter.h
public:
UFUNCTION(BlueprintPure)
int GetCurrentAmmo() const;
void AddHealth(float NewHealth);
bool IsInjured() const;
With this code, you should be able to create Blueprints for HealthPickup and AmmoPickup, and put the VFX inside the blueprints, and adjust the sphere radius to what you see fit. If you have sounds, you can add them in the blueprints.
Don’t hesitate to ask questions, if you want.