This commit is contained in:
John
2026-03-11 11:25:15 +05:30
parent bcd6f827d5
commit 0327abb1a0
77 changed files with 7689 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShooterPickup.h"
#include "Components/SceneComponent.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "ShooterWeaponHolder.h"
#include "ShooterWeapon.h"
#include "Engine/World.h"
#include "TimerManager.h"
AShooterPickup::AShooterPickup()
{
PrimaryActorTick.bCanEverTick = true;
// create the root
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
// create the collision sphere
SphereCollision = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere Collision"));
SphereCollision->SetupAttachment(RootComponent);
SphereCollision->SetRelativeLocation(FVector(0.0f, 0.0f, 84.0f));
SphereCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
SphereCollision->SetCollisionObjectType(ECC_WorldStatic);
SphereCollision->SetCollisionResponseToAllChannels(ECR_Ignore);
SphereCollision->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
SphereCollision->bFillCollisionUnderneathForNavmesh = true;
// subscribe to the collision overlap on the sphere
SphereCollision->OnComponentBeginOverlap.AddDynamic(this, &AShooterPickup::OnOverlap);
// create the mesh
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(SphereCollision);
Mesh->SetCollisionProfileName(FName("NoCollision"));
}
void AShooterPickup::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
if (FWeaponTableRow* WeaponData = WeaponType.GetRow<FWeaponTableRow>(FString()))
{
// set the mesh
Mesh->SetStaticMesh(WeaponData->StaticMesh.LoadSynchronous());
}
}
void AShooterPickup::BeginPlay()
{
Super::BeginPlay();
if (FWeaponTableRow* WeaponData = WeaponType.GetRow<FWeaponTableRow>(FString()))
{
// copy the weapon class
WeaponClass = WeaponData->WeaponToSpawn;
}
}
void AShooterPickup::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// clear the respawn timer
GetWorld()->GetTimerManager().ClearTimer(RespawnTimer);
}
void AShooterPickup::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// have we collided against a weapon holder?
if (IShooterWeaponHolder* WeaponHolder = Cast<IShooterWeaponHolder>(OtherActor))
{
WeaponHolder->AddWeaponClass(WeaponClass);
// hide this mesh
SetActorHiddenInGame(true);
// disable collision
SetActorEnableCollision(false);
// disable ticking
SetActorTickEnabled(false);
// schedule the respawn
GetWorld()->GetTimerManager().SetTimer(RespawnTimer, this, &AShooterPickup::RespawnPickup, RespawnTime, false);
}
}
void AShooterPickup::RespawnPickup()
{
// unhide this pickup
SetActorHiddenInGame(false);
// call the BP handler
BP_OnRespawn();
}
void AShooterPickup::FinishRespawn()
{
// enable collision
SetActorEnableCollision(true);
// enable tick
SetActorTickEnabled(true);
}

View File

@@ -0,0 +1,96 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/DataTable.h"
#include "Engine/StaticMesh.h"
#include "ShooterPickup.generated.h"
class USphereComponent;
class UPrimitiveComponent;
class AShooterWeapon;
/**
* Holds information about a type of weapon pickup
*/
USTRUCT(BlueprintType)
struct FWeaponTableRow : public FTableRowBase
{
GENERATED_BODY()
/** Mesh to display on the pickup */
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UStaticMesh> StaticMesh;
/** Weapon class to grant on pickup */
UPROPERTY(EditAnywhere)
TSubclassOf<AShooterWeapon> WeaponToSpawn;
};
/**
* Simple shooter game weapon pickup
*/
UCLASS(abstract)
class AUDIOVIDEORECORD_API AShooterPickup : public AActor
{
GENERATED_BODY()
/** Collision sphere */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
USphereComponent* SphereCollision;
/** Weapon pickup mesh. Its mesh asset is set from the weapon data table */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* Mesh;
protected:
/** Data on the type of picked weapon and visuals of this pickup */
UPROPERTY(EditAnywhere, Category="Pickup")
FDataTableRowHandle WeaponType;
/** Type to weapon to grant on pickup. Set from the weapon data table. */
TSubclassOf<AShooterWeapon> WeaponClass;
/** Time to wait before respawning this pickup */
UPROPERTY(EditAnywhere, Category="Pickup", meta = (ClampMin = 0, ClampMax = 120, Units = "s"))
float RespawnTime = 4.0f;
/** Timer to respawn the pickup */
FTimerHandle RespawnTimer;
public:
/** Constructor */
AShooterPickup();
protected:
/** Native construction script */
virtual void OnConstruction(const FTransform& Transform) override;
/** Gameplay Initialization*/
virtual void BeginPlay() override;
/** Gameplay cleanup */
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/** Handles collision overlap */
UFUNCTION()
virtual void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
protected:
/** Called when it's time to respawn this pickup */
void RespawnPickup();
/** Passes control to Blueprint to animate the pickup respawn. Should end by calling FinishRespawn */
UFUNCTION(BlueprintImplementableEvent, Category="Pickup", meta = (DisplayName = "OnRespawn"))
void BP_OnRespawn();
/** Enables this pickup after respawning */
UFUNCTION(BlueprintCallable, Category="Pickup")
void FinishRespawn();
};

View File

@@ -0,0 +1,167 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShooterProjectile.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/DamageType.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Controller.h"
#include "Engine/OverlapResult.h"
#include "Engine/World.h"
#include "TimerManager.h"
AShooterProjectile::AShooterProjectile()
{
PrimaryActorTick.bCanEverTick = true;
// create the collision component and assign it as the root
RootComponent = CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("Collision Component"));
CollisionComponent->SetSphereRadius(16.0f);
CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
CollisionComponent->SetCollisionResponseToAllChannels(ECR_Block);
CollisionComponent->CanCharacterStepUpOn = ECanBeCharacterBase::ECB_No;
// create the projectile movement component. No need to attach it because it's not a Scene Component
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Projectile Movement"));
ProjectileMovement->InitialSpeed = 3000.0f;
ProjectileMovement->MaxSpeed = 3000.0f;
ProjectileMovement->bShouldBounce = true;
// set the default damage type
HitDamageType = UDamageType::StaticClass();
}
void AShooterProjectile::BeginPlay()
{
Super::BeginPlay();
// ignore the pawn that shot this projectile
CollisionComponent->IgnoreActorWhenMoving(GetInstigator(), true);
}
void AShooterProjectile::EndPlay(EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// clear the destruction timer
GetWorld()->GetTimerManager().ClearTimer(DestructionTimer);
}
void AShooterProjectile::NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
// ignore if we've already hit something else
if (bHit)
{
return;
}
bHit = true;
// disable collision on the projectile
CollisionComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// make AI perception noise
MakeNoise(NoiseLoudness, GetInstigator(), GetActorLocation(), NoiseRange, NoiseTag);
if (bExplodeOnHit)
{
// apply explosion damage centered on the projectile
ExplosionCheck(GetActorLocation());
} else {
// single hit projectile. Process the collided actor
ProcessHit(Other, OtherComp, Hit.ImpactPoint, -Hit.ImpactNormal);
}
// pass control to BP for any extra effects
BP_OnProjectileHit(Hit);
// check if we should schedule deferred destruction of the projectile
if (DeferredDestructionTime > 0.0f)
{
GetWorld()->GetTimerManager().SetTimer(DestructionTimer, this, &AShooterProjectile::OnDeferredDestruction, DeferredDestructionTime, false);
} else {
// destroy the projectile right away
Destroy();
}
}
void AShooterProjectile::ExplosionCheck(const FVector& ExplosionCenter)
{
// do a sphere overlap check look for nearby actors to damage
TArray<FOverlapResult> Overlaps;
FCollisionShape OverlapShape;
OverlapShape.SetSphere(ExplosionRadius);
FCollisionObjectQueryParams ObjectParams;
ObjectParams.AddObjectTypesToQuery(ECC_Pawn);
ObjectParams.AddObjectTypesToQuery(ECC_WorldDynamic);
ObjectParams.AddObjectTypesToQuery(ECC_PhysicsBody);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(this);
if (!bDamageOwner)
{
QueryParams.AddIgnoredActor(GetInstigator());
}
GetWorld()->OverlapMultiByObjectType(Overlaps, ExplosionCenter, FQuat::Identity, ObjectParams, OverlapShape, QueryParams);
TArray<AActor*> DamagedActors;
// process the overlap results
for (const FOverlapResult& CurrentOverlap : Overlaps)
{
// overlaps may return the same actor multiple times per each component overlapped
// ensure we only damage each actor once by adding it to a damaged list
if (DamagedActors.Find(CurrentOverlap.GetActor()) == INDEX_NONE)
{
DamagedActors.Add(CurrentOverlap.GetActor());
// apply physics force away from the explosion
const FVector& ExplosionDir = CurrentOverlap.GetActor()->GetActorLocation() - GetActorLocation();
// push and/or damage the overlapped actor
ProcessHit(CurrentOverlap.GetActor(), CurrentOverlap.GetComponent(), GetActorLocation(), ExplosionDir.GetSafeNormal());
}
}
}
void AShooterProjectile::ProcessHit(AActor* HitActor, UPrimitiveComponent* HitComp, const FVector& HitLocation, const FVector& HitDirection)
{
// have we hit a character?
if (ACharacter* HitCharacter = Cast<ACharacter>(HitActor))
{
// ignore the owner of this projectile
if (HitCharacter != GetOwner() || bDamageOwner)
{
// apply damage to the character
UGameplayStatics::ApplyDamage(HitCharacter, HitDamage, GetInstigator()->GetController(), this, HitDamageType);
}
}
// have we hit a physics object?
if (HitComp->IsSimulatingPhysics())
{
// give some physics impulse to the object
HitComp->AddImpulseAtLocation(HitDirection * PhysicsForce, HitLocation);
}
}
void AShooterProjectile::OnDeferredDestruction()
{
// destroy this actor
Destroy();
}

View File

@@ -0,0 +1,109 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ShooterProjectile.generated.h"
class USphereComponent;
class UProjectileMovementComponent;
class ACharacter;
class UPrimitiveComponent;
/**
* Simple projectile class for a first person shooter game
*/
UCLASS(abstract)
class AUDIOVIDEORECORD_API AShooterProjectile : public AActor
{
GENERATED_BODY()
/** Provides collision detection for the projectile */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
USphereComponent* CollisionComponent;
/** Handles movement for the projectile */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UProjectileMovementComponent* ProjectileMovement;
protected:
/** Loudness of the AI perception noise done by this projectile on hit */
UPROPERTY(EditAnywhere, Category="Projectile|Noise", meta = (ClampMin = 0, ClampMax = 100))
float NoiseLoudness = 3.0f;
/** Range of the AI perception noise done by this projectile on hit */
UPROPERTY(EditAnywhere, Category="Projectile|Noise", meta = (ClampMin = 0, ClampMax = 100000, Units = "cm"))
float NoiseRange = 3000.0f;
/** Tag of the AI perception noise done by this projectile on hit */
UPROPERTY(EditAnywhere, Category="Noise")
FName NoiseTag = FName("Projectile");
/** Physics force to apply on hit */
UPROPERTY(EditAnywhere, Category="Projectile|Hit", meta = (ClampMin = 0, ClampMax = 50000))
float PhysicsForce = 100.0f;
/** Damage to apply on hit */
UPROPERTY(EditAnywhere, Category="Projectile|Hit", meta = (ClampMin = 0, ClampMax = 100))
float HitDamage = 25.0f;
/** Type of damage to apply. Can be used to represent specific types of damage such as fire, explosion, etc. */
UPROPERTY(EditAnywhere, Category="Projectile|Hit")
TSubclassOf<UDamageType> HitDamageType;
/** If true, the projectile can damage the character that shot it */
UPROPERTY(EditAnywhere, Category="Projectile|Hit")
bool bDamageOwner = false;
/** If true, the projectile will explode and apply radial damage to all actors in range */
UPROPERTY(EditAnywhere, Category="Projectile|Explosion")
bool bExplodeOnHit = false;
/** Max distance for actors to be affected by explosion damage */
UPROPERTY(EditAnywhere, Category="Projectile|Explosion", meta = (ClampMin = 0, ClampMax = 5000, Units = "cm"))
float ExplosionRadius = 500.0f;
/** If true, this projectile has already hit another surface */
bool bHit = false;
/** How long to wait after a hit before destroying this projectile */
UPROPERTY(EditAnywhere, Category="Projectile|Destruction", meta = (ClampMin = 0, ClampMax = 10, Units = "s"))
float DeferredDestructionTime = 5.0f;
/** Timer to handle deferred destruction of this projectile */
FTimerHandle DestructionTimer;
public:
/** Constructor */
AShooterProjectile();
protected:
/** Gameplay initialization */
virtual void BeginPlay() override;
/** Gameplay cleanup */
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
/** Handles collision */
virtual void NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override;
protected:
/** Looks up actors within the explosion radius and damages them */
void ExplosionCheck(const FVector& ExplosionCenter);
/** Processes a projectile hit for the given actor */
void ProcessHit(AActor* HitActor, UPrimitiveComponent* HitComp, const FVector& HitLocation, const FVector& HitDirection);
/** Passes control to Blueprint to implement any effects on hit. */
UFUNCTION(BlueprintImplementableEvent, Category="Projectile", meta = (DisplayName = "On Projectile Hit"))
void BP_OnProjectileHit(const FHitResult& Hit);
/** Called from the destruction timer to destroy this projectile */
void OnDeferredDestruction();
};

View File

@@ -0,0 +1,218 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShooterWeapon.h"
#include "Kismet/KismetMathLibrary.h"
#include "Engine/World.h"
#include "ShooterProjectile.h"
#include "ShooterWeaponHolder.h"
#include "Components/SceneComponent.h"
#include "TimerManager.h"
#include "Animation/AnimInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Pawn.h"
AShooterWeapon::AShooterWeapon()
{
PrimaryActorTick.bCanEverTick = true;
// create the root
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
// create the first person mesh
FirstPersonMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("First Person Mesh"));
FirstPersonMesh->SetupAttachment(RootComponent);
FirstPersonMesh->SetCollisionProfileName(FName("NoCollision"));
FirstPersonMesh->SetFirstPersonPrimitiveType(EFirstPersonPrimitiveType::FirstPerson);
FirstPersonMesh->bOnlyOwnerSee = true;
// create the third person mesh
ThirdPersonMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Third Person Mesh"));
ThirdPersonMesh->SetupAttachment(RootComponent);
ThirdPersonMesh->SetCollisionProfileName(FName("NoCollision"));
ThirdPersonMesh->SetFirstPersonPrimitiveType(EFirstPersonPrimitiveType::WorldSpaceRepresentation);
ThirdPersonMesh->bOwnerNoSee = true;
}
void AShooterWeapon::BeginPlay()
{
Super::BeginPlay();
// subscribe to the owner's destroyed delegate
GetOwner()->OnDestroyed.AddDynamic(this, &AShooterWeapon::OnOwnerDestroyed);
// cast the weapon owner
WeaponOwner = Cast<IShooterWeaponHolder>(GetOwner());
PawnOwner = Cast<APawn>(GetOwner());
// fill the first ammo clip
CurrentBullets = MagazineSize;
// attach the meshes to the owner
WeaponOwner->AttachWeaponMeshes(this);
}
void AShooterWeapon::EndPlay(EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// clear the refire timer
GetWorld()->GetTimerManager().ClearTimer(RefireTimer);
}
void AShooterWeapon::OnOwnerDestroyed(AActor* DestroyedActor)
{
// ensure this weapon is destroyed when the owner is destroyed
Destroy();
}
void AShooterWeapon::ActivateWeapon()
{
// unhide this weapon
SetActorHiddenInGame(false);
// notify the owner
WeaponOwner->OnWeaponActivated(this);
}
void AShooterWeapon::DeactivateWeapon()
{
// ensure we're no longer firing this weapon while deactivated
StopFiring();
// hide the weapon
SetActorHiddenInGame(true);
// notify the owner
WeaponOwner->OnWeaponDeactivated(this);
}
void AShooterWeapon::StartFiring()
{
// raise the firing flag
bIsFiring = true;
// check how much time has passed since we last shot
// this may be under the refire rate if the weapon shoots slow enough and the player is spamming the trigger
const float TimeSinceLastShot = GetWorld()->GetTimeSeconds() - TimeOfLastShot;
if (TimeSinceLastShot > RefireRate)
{
// fire the weapon right away
Fire();
} else {
// if we're full auto, schedule the next shot
if (bFullAuto)
{
GetWorld()->GetTimerManager().SetTimer(RefireTimer, this, &AShooterWeapon::Fire, TimeSinceLastShot, false);
}
}
}
void AShooterWeapon::StopFiring()
{
// lower the firing flag
bIsFiring = false;
// clear the refire timer
GetWorld()->GetTimerManager().ClearTimer(RefireTimer);
}
void AShooterWeapon::Fire()
{
// ensure the player still wants to fire. They may have let go of the trigger
if (!bIsFiring)
{
return;
}
// fire a projectile at the target
FireProjectile(WeaponOwner->GetWeaponTargetLocation());
// update the time of our last shot
TimeOfLastShot = GetWorld()->GetTimeSeconds();
// make noise so the AI perception system can hear us
MakeNoise(ShotLoudness, PawnOwner, PawnOwner->GetActorLocation(), ShotNoiseRange, ShotNoiseTag);
// are we full auto?
if (bFullAuto)
{
// schedule the next shot
GetWorld()->GetTimerManager().SetTimer(RefireTimer, this, &AShooterWeapon::Fire, RefireRate, false);
} else {
// for semi-auto weapons, schedule the cooldown notification
GetWorld()->GetTimerManager().SetTimer(RefireTimer, this, &AShooterWeapon::FireCooldownExpired, RefireRate, false);
}
}
void AShooterWeapon::FireCooldownExpired()
{
// notify the owner
WeaponOwner->OnSemiWeaponRefire();
}
void AShooterWeapon::FireProjectile(const FVector& TargetLocation)
{
// get the projectile transform
FTransform ProjectileTransform = CalculateProjectileSpawnTransform(TargetLocation);
// spawn the projectile
FActorSpawnParameters SpawnParams;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnParams.TransformScaleMethod = ESpawnActorScaleMethod::OverrideRootScale;
SpawnParams.Owner = GetOwner();
SpawnParams.Instigator = PawnOwner;
AShooterProjectile* Projectile = GetWorld()->SpawnActor<AShooterProjectile>(ProjectileClass, ProjectileTransform, SpawnParams);
// play the firing montage
WeaponOwner->PlayFiringMontage(FiringMontage);
// add recoil
WeaponOwner->AddWeaponRecoil(FiringRecoil);
// consume bullets
--CurrentBullets;
// if the clip is depleted, reload it
if (CurrentBullets <= 0)
{
CurrentBullets = MagazineSize;
}
// update the weapon HUD
WeaponOwner->UpdateWeaponHUD(CurrentBullets, MagazineSize);
}
FTransform AShooterWeapon::CalculateProjectileSpawnTransform(const FVector& TargetLocation) const
{
// find the muzzle location
const FVector MuzzleLoc = FirstPersonMesh->GetSocketLocation(MuzzleSocketName);
// calculate the spawn location ahead of the muzzle
const FVector SpawnLoc = MuzzleLoc + ((TargetLocation - MuzzleLoc).GetSafeNormal() * MuzzleOffset);
// find the aim rotation vector while applying some variance to the target
const FRotator AimRot = UKismetMathLibrary::FindLookAtRotation(SpawnLoc, TargetLocation + (UKismetMathLibrary::RandomUnitVector() * AimVariance));
// return the built transform
return FTransform(AimRot, SpawnLoc, FVector::OneVector);
}
const TSubclassOf<UAnimInstance>& AShooterWeapon::GetFirstPersonAnimInstanceClass() const
{
return FirstPersonAnimInstanceClass;
}
const TSubclassOf<UAnimInstance>& AShooterWeapon::GetThirdPersonAnimInstanceClass() const
{
return ThirdPersonAnimInstanceClass;
}

View File

@@ -0,0 +1,180 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ShooterWeaponHolder.h"
#include "Animation/AnimInstance.h"
#include "ShooterWeapon.generated.h"
class IShooterWeaponHolder;
class AShooterProjectile;
class USkeletalMeshComponent;
class UAnimMontage;
class UAnimInstance;
/**
* Base class for a simple first person shooter weapon
* Provides both first person and third person perspective meshes
* Handles ammo and firing logic
* Interacts with the weapon owner through the ShooterWeaponHolder interface
*/
UCLASS(abstract)
class AUDIOVIDEORECORD_API AShooterWeapon : public AActor
{
GENERATED_BODY()
/** First person perspective mesh */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
USkeletalMeshComponent* FirstPersonMesh;
/** Third person perspective mesh */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
USkeletalMeshComponent* ThirdPersonMesh;
protected:
/** Cast pointer to the weapon owner */
IShooterWeaponHolder* WeaponOwner;
/** Type of projectiles this weapon will shoot */
UPROPERTY(EditAnywhere, Category="Ammo")
TSubclassOf<AShooterProjectile> ProjectileClass;
/** Number of bullets in a magazine */
UPROPERTY(EditAnywhere, Category="Ammo", meta = (ClampMin = 0, ClampMax = 100))
int32 MagazineSize = 10;
/** Number of bullets in the current magazine */
int32 CurrentBullets = 0;
/** Animation montage to play when firing this weapon */
UPROPERTY(EditAnywhere, Category="Animation")
UAnimMontage* FiringMontage;
/** AnimInstance class to set for the first person character mesh when this weapon is active */
UPROPERTY(EditAnywhere, Category="Animation")
TSubclassOf<UAnimInstance> FirstPersonAnimInstanceClass;
/** AnimInstance class to set for the third person character mesh when this weapon is active */
UPROPERTY(EditAnywhere, Category="Animation")
TSubclassOf<UAnimInstance> ThirdPersonAnimInstanceClass;
/** Cone half-angle for variance while aiming */
UPROPERTY(EditAnywhere, Category="Aim", meta = (ClampMin = 0, ClampMax = 90, Units = "Degrees"))
float AimVariance = 0.0f;
/** Amount of firing recoil to apply to the owner */
UPROPERTY(EditAnywhere, Category="Aim", meta = (ClampMin = 0, ClampMax = 100))
float FiringRecoil = 0.0f;
/** Name of the first person muzzle socket where projectiles will spawn */
UPROPERTY(EditAnywhere, Category="Aim")
FName MuzzleSocketName;
/** Distance ahead of the muzzle that bullets will spawn at */
UPROPERTY(EditAnywhere, Category="Aim", meta = (ClampMin = 0, ClampMax = 1000, Units = "cm"))
float MuzzleOffset = 10.0f;
/** If true, this weapon will automatically fire at the refire rate */
UPROPERTY(EditAnywhere, Category="Refire")
bool bFullAuto = false;
/** Time between shots for this weapon. Affects both full auto and semi auto modes */
UPROPERTY(EditAnywhere, Category="Refire", meta = (ClampMin = 0, ClampMax = 5, Units = "s"))
float RefireRate = 0.5f;
/** Game time of last shot fired, used to enforce refire rate on semi auto */
float TimeOfLastShot = 0.0f;
/** If true, the weapon is currently firing */
bool bIsFiring = false;
/** Timer to handle full auto refiring */
FTimerHandle RefireTimer;
/** Cast pawn pointer to the owner for AI perception system interactions */
TObjectPtr<APawn> PawnOwner;
/** Loudness of the shot for AI perception system interactions */
UPROPERTY(EditAnywhere, Category="Perception", meta = (ClampMin = 0, ClampMax = 100))
float ShotLoudness = 1.0f;
/** Max range of shot AI perception noise */
UPROPERTY(EditAnywhere, Category="Perception", meta = (ClampMin = 0, ClampMax = 100000, Units = "cm"))
float ShotNoiseRange = 3000.0f;
/** Tag to apply to noise generated by shooting this weapon */
UPROPERTY(EditAnywhere, Category="Perception")
FName ShotNoiseTag = FName("Shot");
public:
/** Constructor */
AShooterWeapon();
protected:
/** Gameplay initialization */
virtual void BeginPlay() override;
/** Gameplay Cleanup */
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
protected:
/** Called when the weapon's owner is destroyed */
UFUNCTION()
void OnOwnerDestroyed(AActor* DestroyedActor);
public:
/** Activates this weapon and gets it ready to fire */
void ActivateWeapon();
/** Deactivates this weapon */
void DeactivateWeapon();
/** Start firing this weapon */
void StartFiring();
/** Stop firing this weapon */
void StopFiring();
protected:
/** Fire the weapon */
virtual void Fire();
/** Called when the refire rate time has passed while shooting semi auto weapons */
void FireCooldownExpired();
/** Fire a projectile towards the target location */
virtual void FireProjectile(const FVector& TargetLocation);
/** Calculates the spawn transform for projectiles shot by this weapon */
FTransform CalculateProjectileSpawnTransform(const FVector& TargetLocation) const;
public:
/** Returns the first person mesh */
UFUNCTION(BlueprintPure, Category="Weapon")
USkeletalMeshComponent* GetFirstPersonMesh() const { return FirstPersonMesh; };
/** Returns the third person mesh */
UFUNCTION(BlueprintPure, Category="Weapon")
USkeletalMeshComponent* GetThirdPersonMesh() const { return ThirdPersonMesh; };
/** Returns the first person anim instance class */
const TSubclassOf<UAnimInstance>& GetFirstPersonAnimInstanceClass() const;
/** Returns the third person anim instance class */
const TSubclassOf<UAnimInstance>& GetThirdPersonAnimInstanceClass() const;
/** Returns the magazine size */
int32 GetMagazineSize() const { return MagazineSize; };
/** Returns the current bullet count */
int32 GetBulletCount() const { return CurrentBullets; }
};

View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShooterWeaponHolder.h"
// Add default functionality here for any IShooterWeaponHolder functions that are not pure virtual.

View File

@@ -0,0 +1,55 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ShooterWeaponHolder.generated.h"
class AShooterWeapon;
class UAnimMontage;
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UShooterWeaponHolder : public UInterface
{
GENERATED_BODY()
};
/**
* Common interface for Shooter Game weapon holder classes
*/
class AUDIOVIDEORECORD_API IShooterWeaponHolder
{
GENERATED_BODY()
public:
/** Attaches a weapon's meshes to the owner */
virtual void AttachWeaponMeshes(AShooterWeapon* Weapon) = 0;
/** Plays the firing montage for the weapon */
virtual void PlayFiringMontage(UAnimMontage* Montage) = 0;
/** Applies weapon recoil to the owner */
virtual void AddWeaponRecoil(float Recoil) = 0;
/** Updates the weapon's HUD with the current ammo count */
virtual void UpdateWeaponHUD(int32 CurrentAmmo, int32 MagazineSize) = 0;
/** Calculates and returns the aim location for the weapon */
virtual FVector GetWeaponTargetLocation() = 0;
/** Gives a weapon of this class to the owner */
virtual void AddWeaponClass(const TSubclassOf<AShooterWeapon>& WeaponClass) = 0;
/** Activates the passed weapon */
virtual void OnWeaponActivated(AShooterWeapon* Weapon) = 0;
/** Deactivates the passed weapon */
virtual void OnWeaponDeactivated(AShooterWeapon* Weapon) = 0;
/** Notifies the owner that the weapon cooldown has expired and it's ready to shoot again */
virtual void OnSemiWeaponRefire() = 0;
};