534 lines
14 KiB
C++
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;
|
|
}
|
|
}
|
|
|