284 lines
7.0 KiB
C++
284 lines
7.0 KiB
C++
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
|
||
|
|
|
||
|
|
#include "ShooterCharacter.h"
|
||
|
|
#include "ShooterWeapon.h"
|
||
|
|
#include "EnhancedInputComponent.h"
|
||
|
|
#include "Components/InputComponent.h"
|
||
|
|
#include "Components/PawnNoiseEmitterComponent.h"
|
||
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
||
|
|
#include "Components/SkeletalMeshComponent.h"
|
||
|
|
#include "Engine/World.h"
|
||
|
|
#include "Camera/CameraComponent.h"
|
||
|
|
#include "TimerManager.h"
|
||
|
|
#include "ShooterGameMode.h"
|
||
|
|
|
||
|
|
AShooterCharacter::AShooterCharacter()
|
||
|
|
{
|
||
|
|
// create the noise emitter component
|
||
|
|
PawnNoiseEmitter = CreateDefaultSubobject<UPawnNoiseEmitterComponent>(TEXT("Pawn Noise Emitter"));
|
||
|
|
|
||
|
|
// configure movement
|
||
|
|
GetCharacterMovement()->RotationRate = FRotator(0.0f, 600.0f, 0.0f);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::BeginPlay()
|
||
|
|
{
|
||
|
|
Super::BeginPlay();
|
||
|
|
|
||
|
|
// reset HP to max
|
||
|
|
CurrentHP = MaxHP;
|
||
|
|
|
||
|
|
// update the HUD
|
||
|
|
OnDamaged.Broadcast(1.0f);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::EndPlay(EEndPlayReason::Type EndPlayReason)
|
||
|
|
{
|
||
|
|
Super::EndPlay(EndPlayReason);
|
||
|
|
|
||
|
|
// clear the respawn timer
|
||
|
|
GetWorld()->GetTimerManager().ClearTimer(RespawnTimer);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||
|
|
{
|
||
|
|
// base class handles move, aim and jump inputs
|
||
|
|
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||
|
|
|
||
|
|
// Set up action bindings
|
||
|
|
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
||
|
|
{
|
||
|
|
// Firing
|
||
|
|
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &AShooterCharacter::DoStartFiring);
|
||
|
|
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Completed, this, &AShooterCharacter::DoStopFiring);
|
||
|
|
|
||
|
|
// Switch weapon
|
||
|
|
EnhancedInputComponent->BindAction(SwitchWeaponAction, ETriggerEvent::Triggered, this, &AShooterCharacter::DoSwitchWeapon);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
float AShooterCharacter::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
|
||
|
|
{
|
||
|
|
// ignore if already dead
|
||
|
|
if (CurrentHP <= 0.0f)
|
||
|
|
{
|
||
|
|
return 0.0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reduce HP
|
||
|
|
CurrentHP -= Damage;
|
||
|
|
|
||
|
|
// Have we depleted HP?
|
||
|
|
if (CurrentHP <= 0.0f)
|
||
|
|
{
|
||
|
|
Die();
|
||
|
|
}
|
||
|
|
|
||
|
|
// update the HUD
|
||
|
|
OnDamaged.Broadcast(FMath::Max(0.0f, CurrentHP / MaxHP));
|
||
|
|
|
||
|
|
return Damage;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::DoStartFiring()
|
||
|
|
{
|
||
|
|
// fire the current weapon
|
||
|
|
if (CurrentWeapon)
|
||
|
|
{
|
||
|
|
CurrentWeapon->StartFiring();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::DoStopFiring()
|
||
|
|
{
|
||
|
|
// stop firing the current weapon
|
||
|
|
if (CurrentWeapon)
|
||
|
|
{
|
||
|
|
CurrentWeapon->StopFiring();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::DoSwitchWeapon()
|
||
|
|
{
|
||
|
|
// ensure we have at least two weapons two switch between
|
||
|
|
if (OwnedWeapons.Num() > 1)
|
||
|
|
{
|
||
|
|
// deactivate the old weapon
|
||
|
|
CurrentWeapon->DeactivateWeapon();
|
||
|
|
|
||
|
|
// find the index of the current weapon in the owned list
|
||
|
|
int32 WeaponIndex = OwnedWeapons.Find(CurrentWeapon);
|
||
|
|
|
||
|
|
// is this the last weapon?
|
||
|
|
if (WeaponIndex == OwnedWeapons.Num() - 1)
|
||
|
|
{
|
||
|
|
// loop back to the beginning of the array
|
||
|
|
WeaponIndex = 0;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// select the next weapon index
|
||
|
|
++WeaponIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// set the new weapon as current
|
||
|
|
CurrentWeapon = OwnedWeapons[WeaponIndex];
|
||
|
|
|
||
|
|
// activate the new weapon
|
||
|
|
CurrentWeapon->ActivateWeapon();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::AttachWeaponMeshes(AShooterWeapon* Weapon)
|
||
|
|
{
|
||
|
|
const FAttachmentTransformRules AttachmentRule(EAttachmentRule::SnapToTarget, false);
|
||
|
|
|
||
|
|
// attach the weapon actor
|
||
|
|
Weapon->AttachToActor(this, AttachmentRule);
|
||
|
|
|
||
|
|
// attach the weapon meshes
|
||
|
|
Weapon->GetFirstPersonMesh()->AttachToComponent(GetFirstPersonMesh(), AttachmentRule, FirstPersonWeaponSocket);
|
||
|
|
Weapon->GetThirdPersonMesh()->AttachToComponent(GetMesh(), AttachmentRule, FirstPersonWeaponSocket);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::PlayFiringMontage(UAnimMontage* Montage)
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::AddWeaponRecoil(float Recoil)
|
||
|
|
{
|
||
|
|
// apply the recoil as pitch input
|
||
|
|
AddControllerPitchInput(Recoil);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::UpdateWeaponHUD(int32 CurrentAmmo, int32 MagazineSize)
|
||
|
|
{
|
||
|
|
OnBulletCountUpdated.Broadcast(MagazineSize, CurrentAmmo);
|
||
|
|
}
|
||
|
|
|
||
|
|
FVector AShooterCharacter::GetWeaponTargetLocation()
|
||
|
|
{
|
||
|
|
// trace ahead from the camera viewpoint
|
||
|
|
FHitResult OutHit;
|
||
|
|
|
||
|
|
const FVector Start = GetFirstPersonCameraComponent()->GetComponentLocation();
|
||
|
|
const FVector End = Start + (GetFirstPersonCameraComponent()->GetForwardVector() * MaxAimDistance);
|
||
|
|
|
||
|
|
FCollisionQueryParams QueryParams;
|
||
|
|
QueryParams.AddIgnoredActor(this);
|
||
|
|
|
||
|
|
GetWorld()->LineTraceSingleByChannel(OutHit, Start, End, ECC_Visibility, QueryParams);
|
||
|
|
|
||
|
|
// return either the impact point or the trace end
|
||
|
|
return OutHit.bBlockingHit ? OutHit.ImpactPoint : OutHit.TraceEnd;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::AddWeaponClass(const TSubclassOf<AShooterWeapon>& WeaponClass)
|
||
|
|
{
|
||
|
|
// do we already own this weapon?
|
||
|
|
AShooterWeapon* OwnedWeapon = FindWeaponOfType(WeaponClass);
|
||
|
|
|
||
|
|
if (!OwnedWeapon)
|
||
|
|
{
|
||
|
|
// spawn the new weapon
|
||
|
|
FActorSpawnParameters SpawnParams;
|
||
|
|
SpawnParams.Owner = this;
|
||
|
|
SpawnParams.Instigator = this;
|
||
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||
|
|
SpawnParams.TransformScaleMethod = ESpawnActorScaleMethod::MultiplyWithRoot;
|
||
|
|
|
||
|
|
AShooterWeapon* AddedWeapon = GetWorld()->SpawnActor<AShooterWeapon>(WeaponClass, GetActorTransform(), SpawnParams);
|
||
|
|
|
||
|
|
if (AddedWeapon)
|
||
|
|
{
|
||
|
|
// add the weapon to the owned list
|
||
|
|
OwnedWeapons.Add(AddedWeapon);
|
||
|
|
|
||
|
|
// if we have an existing weapon, deactivate it
|
||
|
|
if (CurrentWeapon)
|
||
|
|
{
|
||
|
|
CurrentWeapon->DeactivateWeapon();
|
||
|
|
}
|
||
|
|
|
||
|
|
// switch to the new weapon
|
||
|
|
CurrentWeapon = AddedWeapon;
|
||
|
|
CurrentWeapon->ActivateWeapon();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::OnWeaponActivated(AShooterWeapon* Weapon)
|
||
|
|
{
|
||
|
|
// update the bullet counter
|
||
|
|
OnBulletCountUpdated.Broadcast(Weapon->GetMagazineSize(), Weapon->GetBulletCount());
|
||
|
|
|
||
|
|
// set the character mesh AnimInstances
|
||
|
|
GetFirstPersonMesh()->SetAnimInstanceClass(Weapon->GetFirstPersonAnimInstanceClass());
|
||
|
|
GetMesh()->SetAnimInstanceClass(Weapon->GetThirdPersonAnimInstanceClass());
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::OnWeaponDeactivated(AShooterWeapon* Weapon)
|
||
|
|
{
|
||
|
|
// unused
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::OnSemiWeaponRefire()
|
||
|
|
{
|
||
|
|
// unused
|
||
|
|
}
|
||
|
|
|
||
|
|
AShooterWeapon* AShooterCharacter::FindWeaponOfType(TSubclassOf<AShooterWeapon> WeaponClass) const
|
||
|
|
{
|
||
|
|
// check each owned weapon
|
||
|
|
for (AShooterWeapon* Weapon : OwnedWeapons)
|
||
|
|
{
|
||
|
|
if (Weapon->IsA(WeaponClass))
|
||
|
|
{
|
||
|
|
return Weapon;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// weapon not found
|
||
|
|
return nullptr;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::Die()
|
||
|
|
{
|
||
|
|
// deactivate the weapon
|
||
|
|
if (IsValid(CurrentWeapon))
|
||
|
|
{
|
||
|
|
CurrentWeapon->DeactivateWeapon();
|
||
|
|
}
|
||
|
|
|
||
|
|
// increment the team score
|
||
|
|
if (AShooterGameMode* GM = Cast<AShooterGameMode>(GetWorld()->GetAuthGameMode()))
|
||
|
|
{
|
||
|
|
GM->IncrementTeamScore(TeamByte);
|
||
|
|
}
|
||
|
|
|
||
|
|
// stop character movement
|
||
|
|
GetCharacterMovement()->StopMovementImmediately();
|
||
|
|
|
||
|
|
// disable controls
|
||
|
|
DisableInput(nullptr);
|
||
|
|
|
||
|
|
// reset the bullet counter UI
|
||
|
|
OnBulletCountUpdated.Broadcast(0, 0);
|
||
|
|
|
||
|
|
// call the BP handler
|
||
|
|
BP_OnDeath();
|
||
|
|
|
||
|
|
// schedule character respawn
|
||
|
|
GetWorld()->GetTimerManager().SetTimer(RespawnTimer, this, &AShooterCharacter::OnRespawn, RespawnTime, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AShooterCharacter::OnRespawn()
|
||
|
|
{
|
||
|
|
// destroy the character to force the PC to respawn
|
||
|
|
Destroy();
|
||
|
|
}
|