init
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
180
Source/AudioVideoRecord/Variant_Shooter/Weapons/ShooterWeapon.h
Normal file
180
Source/AudioVideoRecord/Variant_Shooter/Weapons/ShooterWeapon.h
Normal 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; }
|
||||
};
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user