super-duper-spoon/Source/Shooter/ShooterCharacter.cpp

534 lines
14 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "ShooterCharacter.h"
#include "Item.h"
#include "Camera/CameraComponent.h"
#include "Components/WidgetComponent.h"
#include "Engine/SkeletalMeshSocket.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"
#include "Sound/SoundCue.h"
// Sets default values
AShooterCharacter::AShooterCharacter() :
// Base rates for turning/looking up
BaseTurnRate(45.f),
BaseLookUpRate(45.f),
// Turn rates for aiming/not aiming
HipTurnRate(90.f),
HipLookUpRate(90.f),
AimingTurnRate(20.f),
AimingLookUpRate(20.f),
// Mouse look sensitivity scale factors
MouseHipTurnRate(1.0f),
MouseHipLookUpRate(1.0f),
MouseAimingTurnRate(0.2f),
MouseAimingLookUpRate(0.2f),
// True when aiming the weapon
bAiming(false),
// Camera field of view values
CameraDefaultFOV(0.f), // Set in BeginPlay
CameraZoomedFOV(35.f),
CameraCurrentFOV(0.f),
ZoomInterpSpeed(20.f),
// Crosshair spread factors
CrosshairSpreadMultiplier(0.f),
CrosshairVelocityFactor(0.f),
CrosshairInAirFactor(0.f),
CrosshairAimFactor(0.f),
CrosshairShootingFactor(0.f),
// Bullet fire timer variables
ShootTimeDuration(0.05f),
bFiringBullet(false),
// Automatic fire
bShouldFire(true),
bFireButtonPressed(false),
AutomaticFireRate(0.1f),
// Item trace variables
bShouldTraceForItems(false)
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create a camera boom (pulls in towards the character if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
// The camera follows at this distance behind the character
CameraBoom->TargetArmLength = 180.f;
// Rotate the arm based on the controller
CameraBoom->bUsePawnControlRotation = true;
CameraBoom->SocketOffset = FVector(0.f, 50.f, 70.f);
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
// Camera does not rotate relative to arm
FollowCamera->bUsePawnControlRotation = false;
// Don't rotate when the controller does. Let the controller only affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = true;
bUseControllerRotationRoll = false;
GetCharacterMovement()->bOrientRotationToMovement = false; // Character moves in the direction of input....
GetCharacterMovement()->RotationRate = FRotator(0.f, 540.f, 0.f); // ... at this rotation rate
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
}
// Called when the game starts or when spawned
void AShooterCharacter::BeginPlay()
{
Super::BeginPlay();
if (FollowCamera)
{
CameraDefaultFOV = FollowCamera->FieldOfView;
CameraCurrentFOV = CameraDefaultFOV;
}
}
void AShooterCharacter::MoveForward(float Value)
{
if (Controller && (Value != 0))
{
// Find out which way is forward
const FRotator Rotation{ Controller->GetControlRotation() };
const FRotator YawRotation{ 0, Rotation.Yaw, 0 };
const FVector Direction{ FRotationMatrix{YawRotation}.GetUnitAxis(EAxis::X) };
AddMovementInput(Direction, Value);
}
}
void AShooterCharacter::MoveRight(float Value)
{
if (Controller && (Value != 0))
{
// Find out which way is forward
const FRotator Rotation{ Controller->GetControlRotation() };
const FRotator YawRotation{ 0, Rotation.Yaw, 0 };
const FVector Direction{ FRotationMatrix{YawRotation}.GetUnitAxis(EAxis::Y) };
AddMovementInput(Direction, Value);
}
}
void AShooterCharacter::TurnAtRate(float Rate)
{
// Calculate delta for this frame from the rate information
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds()); // def/sec * sec/frame
}
void AShooterCharacter::LookUpAtRate(float Rate)
{
AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds()); // def/sec * sec/frame
}
void AShooterCharacter::Turn(float Value)
{
float TurnScaleFactor;
if (bAiming)
{
TurnScaleFactor = MouseAimingTurnRate;
}
else
{
TurnScaleFactor = MouseHipTurnRate;
}
AddControllerYawInput(Value * TurnScaleFactor);
}
void AShooterCharacter::LookUp(float Value)
{
float LookUpScaleFactor;
if (bAiming)
{
LookUpScaleFactor = MouseAimingLookUpRate;
}
else
{
LookUpScaleFactor = MouseHipLookUpRate;
}
AddControllerPitchInput(Value * LookUpScaleFactor);
}
void AShooterCharacter::FireWeapon()
{
if (FireSound)
{
UGameplayStatics::PlaySound2D(this, FireSound);
}
const USkeletalMeshSocket* BarrelSocket = GetMesh()->GetSocketByName("BarrelSocket");
if (BarrelSocket)
{
const FTransform SocketTransform = BarrelSocket->GetSocketTransform(GetMesh());
if (MuzzleFlash)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlash, SocketTransform);
}
FVector BeamEnd;
bool bBeamEnd = GetBeamEndLocation(SocketTransform.GetLocation(),
BeamEnd);
if (bBeamEnd)
{
// Spawn impact particles after updating BeamEndPoint
if (ImpactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticles,
BeamEnd);
}
// Spawn smoke trail particles
if (BeamParticles)
{
UParticleSystemComponent* Beam = UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BeamParticles,
SocketTransform);
if (Beam)
{
Beam->SetVectorParameter("Target", BeamEnd);
}
}
}
}
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && HipFireMontage)
{
AnimInstance->Montage_Play(HipFireMontage);
AnimInstance->Montage_JumpToSection(FName("StartFire"));
}
// Start bullet fire timer for crosshairs
StartCrosshairBulletFire();
}
bool AShooterCharacter::GetBeamEndLocation(const FVector& MuzzleSocketLocation, FVector& OutBeamLocation)
{
// Check for crosshair trace hit
FHitResult CrosshairHitResult;
bool bCrosshairHit = TraceUnderCrosshairs(CrosshairHitResult, OutBeamLocation);
// Perform a second trace, this time from the gun barrel
FHitResult WeaponTraceHit;
const FVector WeaponTraceStart{ MuzzleSocketLocation };
const FVector StartToEnd{ OutBeamLocation - MuzzleSocketLocation };
const FVector WeaponTraceEnd{ MuzzleSocketLocation + StartToEnd * 1.25f };
GetWorld()->LineTraceSingleByChannel(WeaponTraceHit, WeaponTraceStart, WeaponTraceEnd,
ECollisionChannel::ECC_Visibility);
if (WeaponTraceHit.bBlockingHit) // Object between barrel and BeamEndPoint ?
{
OutBeamLocation = WeaponTraceHit.Location;
return true;
}
return false;
}
void AShooterCharacter::AimingButtonPressed()
{
bAiming = true;
}
void AShooterCharacter::AimingButtonReleased()
{
bAiming = false;
}
void AShooterCharacter::CameraInterpZoom(float DeltaTime)
{
// Set current camera field of view
if (bAiming)
{
// Interpolate to zoomed FOV
CameraCurrentFOV = FMath::FInterpTo(CameraCurrentFOV, CameraZoomedFOV,
DeltaTime, ZoomInterpSpeed);
}
else
{
// Interpolate to default FOV
CameraCurrentFOV = FMath::FInterpTo(CameraCurrentFOV, CameraDefaultFOV,
DeltaTime, ZoomInterpSpeed);
}
if (FollowCamera)
{
FollowCamera->SetFieldOfView(CameraCurrentFOV);
}
}
void AShooterCharacter::SetLookRates()
{
if (bAiming)
{
BaseTurnRate = AimingTurnRate;
BaseLookUpRate = AimingLookUpRate;
}
else
{
BaseTurnRate = HipTurnRate;
BaseLookUpRate = HipLookUpRate;
}
}
void AShooterCharacter::CalculateCrosshairSpread(float DeltaTime)
{
FVector2D WalkSpeedRange{ 0.f, 600.f };
FVector2D VelocityMultiplierRange{ 0.f, 1.f };
FVector Velocity{ GetVelocity() };
Velocity.Z = 0.f;
// Calculate crosshair velocity factor
CrosshairVelocityFactor = FMath::GetMappedRangeValueClamped(WalkSpeedRange,
VelocityMultiplierRange, Velocity.Size());
// Calculate crosshair in air factor
if (GetCharacterMovement()->IsFalling())
{
// Spread the crosshairs quickly while in air
CrosshairInAirFactor = FMath::FInterpTo(CrosshairInAirFactor, 2.25f,
DeltaTime, 20.f);
}
else // Character in on the ground
{
// Spread the crosshairs slowly when on the ground
CrosshairInAirFactor = FMath::FInterpTo(CrosshairInAirFactor, 0,
DeltaTime, 2.25f);
}
// Calculate crosshair aim factor
if (bAiming)
{
// Group the crosshairs when aiming
CrosshairAimFactor = FMath::FInterpTo(CrosshairAimFactor, 0.6,
DeltaTime, 30.f);
}
else
{
// Spread the crosshairs when not aiming
CrosshairAimFactor = FMath::FInterpTo(CrosshairAimFactor, 0,
DeltaTime, 30.f);
}
// True 0.05s after firing
if (bFiringBullet)
{
CrosshairShootingFactor = FMath::FInterpTo(CrosshairShootingFactor, 0.3f,
DeltaTime, 60.f);
}
else
{
CrosshairShootingFactor = FMath::FInterpTo(CrosshairShootingFactor, 0.f,
DeltaTime, 60.f);
}
CrosshairSpreadMultiplier = 0.5f
+ CrosshairVelocityFactor
+ CrosshairInAirFactor
+ CrosshairShootingFactor
- CrosshairAimFactor;
}
void AShooterCharacter::StartCrosshairBulletFire()
{
bFiringBullet = true;
GetWorldTimerManager().SetTimer(CrosshairShootTimer, this,
&AShooterCharacter::FinishCrosshairBulletFire, ShootTimeDuration);
}
void AShooterCharacter::FinishCrosshairBulletFire()
{
bFiringBullet = false;
}
void AShooterCharacter::FireButtonPressed()
{
bFireButtonPressed = true;
StartFireTimer();
}
void AShooterCharacter::FireButtonReleased()
{
bFireButtonPressed = false;
}
void AShooterCharacter::StartFireTimer()
{
if (bShouldFire)
{
AShooterCharacter::FireWeapon();
bShouldFire = false;
GetWorldTimerManager().SetTimer(AutoFireTimer, this, &AShooterCharacter::AutoFireReset,
AutomaticFireRate);
}
}
void AShooterCharacter::AutoFireReset()
{
bShouldFire = true;
if (bFireButtonPressed)
{
StartFireTimer();
}
}
bool AShooterCharacter::TraceUnderCrosshairs(FHitResult& OutHitResult, FVector& OutHitLocation)
{
// Get Viewport Size
FVector2D ViewportSize;
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
// Get screen space location of crosshairs
FVector2D CrosshairLocation{ ViewportSize.X / 2.f, ViewportSize.Y / 2.f };
// Get world position and direction of crosshairs
FVector CrosshairWorldPosition;
FVector CrosshairWorldDirection;
bool bScreenToWorld = UGameplayStatics::DeprojectScreenToWorld(
UGameplayStatics::GetPlayerController(this, 0),
CrosshairLocation, CrosshairWorldPosition, CrosshairWorldDirection);
if (bScreenToWorld)
{
// Trace from Crosshair world location outward
const FVector Start{ CrosshairWorldPosition };
const FVector End{ Start + CrosshairWorldDirection * 50'000.f };
OutHitLocation = End;
GetWorld()->LineTraceSingleByChannel(OutHitResult, Start, End, ECollisionChannel::ECC_Visibility);
if (OutHitResult.bBlockingHit)
{
OutHitLocation = OutHitResult.Location;
return true;
}
}
return false;
}
void AShooterCharacter::TraceForItems()
{
static AItem* HitItem;
if (bShouldTraceForItems)
{
FHitResult ItemTraceResult;
FVector HitLocation;
TraceUnderCrosshairs(ItemTraceResult, HitLocation);
if (ItemTraceResult.bBlockingHit)
{
// If an item was being looked at from last frame, we should hide its widget
SetItemPickupWidgetVisibility(HitItem, false);
// Show the new item's widget
HitItem = Cast<AItem>(ItemTraceResult.GetActor());
SetItemPickupWidgetVisibility(HitItem, true);
}
else if (HitItem) // Hide widget for the item from last frame
{
SetItemPickupWidgetVisibility(HitItem, false);
HitItem = nullptr;
}
}
else if (HitItem) // Hide widget for the item from last frame
{
SetItemPickupWidgetVisibility(HitItem, false);
HitItem = nullptr;
}
}
void AShooterCharacter::SetItemPickupWidgetVisibility(AItem* Item, bool visibility)
{
if (Item && Item->GetPickupWidget())
{
Item->GetPickupWidget()->SetVisibility(visibility);
}
}
// Called every frame
void AShooterCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Handle interpolation for zoom when aiming
CameraInterpZoom(DeltaTime);
// Change look sensitivity based on aiming
SetLookRates();
// Calculate crosshair spread multiplier
CalculateCrosshairSpread(DeltaTime);
TraceForItems();
}
// Called to bind functionality to input
void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
check(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward", this, &AShooterCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AShooterCharacter::MoveRight);
PlayerInputComponent->BindAxis("TurnRate", this, &AShooterCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUpRate", this, &AShooterCharacter::LookUpAtRate);
PlayerInputComponent->BindAxis("Turn", this, &AShooterCharacter::Turn);
PlayerInputComponent->BindAxis("LookUp", this, &AShooterCharacter::LookUp);
PlayerInputComponent->BindAction("Jump", IE_Pressed, this,
&ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this,
&ACharacter::StopJumping);
PlayerInputComponent->BindAction("FireButton", IE_Pressed, this,
&AShooterCharacter::FireButtonPressed);
PlayerInputComponent->BindAction("FireButton", IE_Released, this,
&AShooterCharacter::FireButtonReleased);
PlayerInputComponent->BindAction("Aiming", IE_Pressed, this,
&AShooterCharacter::AimingButtonPressed);
PlayerInputComponent->BindAction("Aiming", IE_Released, this,
&AShooterCharacter::AimingButtonReleased);
}
float AShooterCharacter::GetCrosshairSpreadMultiplier() const
{
return CrosshairSpreadMultiplier;
}
void AShooterCharacter::IncrementOverlappedItemCount(int8 Amount)
{
if (OverlappedItemCount + Amount <= 0)
{
OverlappedItemCount = 0;
bShouldTraceForItems = false;
}
else
{
OverlappedItemCount += Amount;
bShouldTraceForItems = true;
}
}