525 lines
16 KiB
C++
525 lines
16 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
||
|
||
|
||
#include "Player/ExoPlayerController.h"
|
||
#include "EnhancedInputComponent.h"
|
||
#include "EnhancedInputSubsystems.h"
|
||
#include "Characters/ExoPlayerCharacter.h"
|
||
#include "GameFramework/Character.h"
|
||
#include "GameFramework/CharacterMovementComponent.h"
|
||
#include "Kismet/GameplayStatics.h"
|
||
|
||
AExoPlayerController::AExoPlayerController()
|
||
{
|
||
PrimaryActorTick.bCanEverTick = true;
|
||
}
|
||
|
||
void AExoPlayerController::BeginPlay()
|
||
{
|
||
Super::BeginPlay();
|
||
check(InputContext);
|
||
|
||
PlayerCharacter = Cast<AExoPlayerCharacter>(GetPawn<ACharacter>());
|
||
check(PlayerCharacter);
|
||
|
||
UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
|
||
|
||
if (Subsystem)
|
||
{
|
||
Subsystem->AddMappingContext(InputContext, 0);
|
||
}
|
||
|
||
//InteractionComponent = PlayerCharacter->FindComponentByClass<UInteractionComponent>();
|
||
//ShootingComponent = PlayerCharacter->FindComponentByClass<UShootingComponent>();
|
||
InteractionComponent = PlayerCharacter->InteractionComponent;
|
||
ShootingComponent = PlayerCharacter->ShootingComponent;
|
||
|
||
// Ustawianie w komponencie poruszania pr<70>dko<6B>ci zapisanej w characterze
|
||
PlayerCharacter->GetCharacterMovement()->MaxWalkSpeed = PlayerCharacter->WalkSpeed;
|
||
|
||
// Oblicz prawidłową maksymalną wysokość na którą można wychylić się z nad osłony
|
||
|
||
UE_LOG(LogTemp, Display, TEXT("MaxCoverAimHeight: %f"), MaxCoverAimHeight);
|
||
}
|
||
|
||
void AExoPlayerController::PlayerTick(float DeltaTime)
|
||
{
|
||
Super::PlayerTick(DeltaTime);
|
||
|
||
// TESTING
|
||
if (bIsInCover)
|
||
{
|
||
AdjustCameraWhileInCover();
|
||
}
|
||
UpdateCoverStandHeight(DeltaTime);
|
||
|
||
if (bShowCoverSystemDebug)
|
||
{
|
||
GEngine->AddOnScreenDebugMessage(2, -1, FColor::Red, FString::Printf(TEXT("In cover: %s"), bIsInCover ? "true" : "false"));
|
||
}
|
||
}
|
||
|
||
void AExoPlayerController::SetupInputComponent()
|
||
{
|
||
Super::SetupInputComponent();
|
||
|
||
UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
|
||
|
||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AExoPlayerController::Move);
|
||
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AExoPlayerController::Look);
|
||
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &AExoPlayerController::Interact);
|
||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerJump);
|
||
EnhancedInputComponent->BindAction(DodgeAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerDodge);
|
||
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerStartCrouch);
|
||
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AExoPlayerController::PlayerStopCrouch);
|
||
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerStartSprint);
|
||
EnhancedInputComponent->BindAction(SprintAction, ETriggerEvent::Completed, this, &AExoPlayerController::PlayerStopSprint);
|
||
EnhancedInputComponent->BindAction(ShootAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerShoot);
|
||
EnhancedInputComponent->BindAction(ThrowAction, ETriggerEvent::Completed, this, &AExoPlayerController::PlayerThrow);
|
||
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerStartAim);
|
||
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &AExoPlayerController::PlayerStopAim);
|
||
EnhancedInputComponent->BindAction(MeleAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerMeleAttack);
|
||
EnhancedInputComponent->BindAction(ChangeWeaponAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerChangeWeapon);
|
||
EnhancedInputComponent->BindAction(SelectFirstWeaponAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerSelectFirstWeapon);
|
||
EnhancedInputComponent->BindAction(SelectSecondWeaponAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerSelectSecondWeapon);
|
||
EnhancedInputComponent->BindAction(DropWeaponAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerDropWeapon);
|
||
EnhancedInputComponent->BindAction(ReloadAction, ETriggerEvent::Started, this, &AExoPlayerController::PlayerReload);
|
||
}
|
||
|
||
void AExoPlayerController::Move(const FInputActionValue& InputActionValue)
|
||
{
|
||
const FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();
|
||
const FRotator Rotation = GetControlRotation();
|
||
const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);
|
||
|
||
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||
|
||
if (PlayerCharacter)
|
||
{
|
||
PlayerCharacter->AddMovementInput(ForwardDirection, InputAxisVector.X);
|
||
PlayerCharacter->AddMovementInput(RightDirection, InputAxisVector.Y);
|
||
}
|
||
}
|
||
|
||
void AExoPlayerController::Look(const FInputActionValue& InputActionValue)
|
||
{
|
||
FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();
|
||
|
||
AddYawInput(InputAxisVector.X);
|
||
AddPitchInput(InputAxisVector.Y);
|
||
}
|
||
|
||
|
||
void AExoPlayerController::Interact()
|
||
{
|
||
if (InteractionComponent->InteractedActor)
|
||
IInteractable::Execute_Interact(InteractionComponent->InteractedActor, PlayerCharacter);
|
||
}
|
||
|
||
void AExoPlayerController::PlayerJump()
|
||
{
|
||
PlayerCharacter->Jump();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerDodge()
|
||
{
|
||
UE_LOG(LogTemp, Error, TEXT("Player Dodge"));
|
||
|
||
if (PlayerCharacter->GetCharacterMovement()->IsFalling()) return;
|
||
|
||
if (PlayerCharacter->GetCharacterMovement()->IsCrouching()) return;
|
||
|
||
if (!CanDodge) return;
|
||
|
||
CanDodge = false;
|
||
|
||
FVector DodgeDirection = PlayerCharacter->GetVelocity().GetSafeNormal();
|
||
DodgeDirection.Z = 0.f;
|
||
|
||
if (DodgeDirection.IsNearlyZero())
|
||
DodgeDirection = PlayerCharacter->GetActorForwardVector();
|
||
|
||
PlayerCharacter->LaunchCharacter(DodgeDirection * PlayerCharacter->DodgeForce, true, true);
|
||
|
||
GetWorldTimerManager().SetTimer(DodgeCooldownTimer, this, &AExoPlayerController::ResetDodge, PlayerCharacter->DodgeCooldown, false);
|
||
}
|
||
|
||
void AExoPlayerController::ResetDodge()
|
||
{
|
||
CanDodge = true;
|
||
}
|
||
|
||
void AExoPlayerController::PlayerStartCrouch()
|
||
{
|
||
if (bIsSprinting)
|
||
{
|
||
PlayerCharacter->GetCharacterMovement()->MaxWalkSpeedCrouched = PlayerCharacter->SlideSpeed;
|
||
GetWorldTimerManager().SetTimer(SlideCooldownTimer, this, &AExoPlayerController::ResetSlide, PlayerCharacter->SlideCooldown, false);
|
||
}
|
||
|
||
PlayerCharacter->Crouch();
|
||
PlayerCharacter->CameraComponent->SetRelativeLocation(FVector(
|
||
0.0f,
|
||
0.0f,
|
||
PlayerCharacter->CrouchedEyeHeight)
|
||
);
|
||
|
||
// Start checking for cover
|
||
bIsInCover = CheckForCover();
|
||
GetWorld()->GetTimerManager().SetTimer(
|
||
CoverCheckTimer,
|
||
this,
|
||
&AExoPlayerController::OnCoverTimer,
|
||
CoverCheckRate,
|
||
true
|
||
);
|
||
}
|
||
|
||
void AExoPlayerController::PlayerStopCrouch()
|
||
{
|
||
PlayerCharacter->UnCrouch();
|
||
PlayerCharacter->CameraComponent->SetRelativeLocation(FVector(
|
||
0.0f,
|
||
0.0f,
|
||
PlayerCharacter->BaseEyeHeight)
|
||
);
|
||
|
||
// Stop checking for cover
|
||
GetWorld()->GetTimerManager().ClearTimer(CoverCheckTimer);
|
||
bIsInCover = false;
|
||
TargetCoverStandAlpha = 0.0f;
|
||
}
|
||
|
||
void AExoPlayerController::ResetSlide()
|
||
{
|
||
PlayerCharacter->GetCharacterMovement()->MaxWalkSpeedCrouched = PlayerCharacter->CrouchSpeed;
|
||
}
|
||
|
||
void AExoPlayerController::PlayerStartSprint()
|
||
{
|
||
if (!PlayerCharacter->bIsAimingMode)
|
||
{
|
||
UE_LOG(LogTemp, Display, TEXT("Start sprint"));
|
||
|
||
bIsSprinting = true;
|
||
PlayerCharacter->GetCharacterMovement()->MaxWalkSpeed = PlayerCharacter->SprintSpeed;
|
||
}
|
||
}
|
||
|
||
void AExoPlayerController::PlayerStopSprint()
|
||
{
|
||
if (!PlayerCharacter->bIsAimingMode)
|
||
{
|
||
UE_LOG(LogTemp, Display, TEXT("Stop sprint"));
|
||
|
||
bIsSprinting = false;
|
||
PlayerCharacter->GetCharacterMovement()->MaxWalkSpeed = PlayerCharacter->WalkSpeed;
|
||
}
|
||
}
|
||
|
||
void AExoPlayerController::PlayerShoot()
|
||
{
|
||
ShootingComponent->Shoot();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerThrow()
|
||
{
|
||
ShootingComponent->Throw();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerStartAim()
|
||
{
|
||
ShootingComponent->StartAiming();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerStopAim()
|
||
{
|
||
ShootingComponent->StopAiming();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerMeleAttack()
|
||
{
|
||
ShootingComponent->MeleAttack();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerChangeWeapon()
|
||
{
|
||
ShootingComponent->SwitchGun();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerSelectFirstWeapon()
|
||
{
|
||
ShootingComponent->SelectGun(true);
|
||
}
|
||
|
||
void AExoPlayerController::PlayerSelectSecondWeapon()
|
||
{
|
||
ShootingComponent->SelectGun(false);
|
||
}
|
||
|
||
void AExoPlayerController::PlayerDropWeapon()
|
||
{
|
||
ShootingComponent->DropGun();
|
||
}
|
||
|
||
void AExoPlayerController::PlayerReload()
|
||
{
|
||
ShootingComponent->Reload();
|
||
}
|
||
|
||
bool AExoPlayerController::CheckForCover()
|
||
{
|
||
// Cover is recalculated every time the player crouches or stops moving while crouched
|
||
// Cover targets are cleared completely when player stands up again or dies
|
||
|
||
// Do a simple 8-directional line trace
|
||
FVector TraceDirection = PlayerCharacter->GetActorForwardVector();
|
||
FVector TraceStart = PlayerCharacter->GetActorLocation();
|
||
TraceStart.Z -= PlayerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() - CoverObstacleMinHeight;
|
||
FCollisionQueryParams QueryParams;
|
||
QueryParams.AddIgnoredActor(PlayerCharacter);
|
||
|
||
TArray<FHitResult> ValidHits;
|
||
for (int i = 0; i <= 8; i++)
|
||
{
|
||
// Trace for possible cover at set distance
|
||
FHitResult Hit;
|
||
FVector TraceEnd = TraceStart + (TraceDirection * MaxDistanceFromCover);
|
||
GetWorld()->LineTraceSingleByChannel(
|
||
Hit,
|
||
TraceStart,
|
||
TraceEnd,
|
||
CoverTraceChannel,
|
||
QueryParams
|
||
);
|
||
|
||
if (bShowCoverSystemDebug)
|
||
{
|
||
// DEBUG
|
||
DrawDebugLine(GetWorld(), TraceStart,TraceEnd,
|
||
Hit.bBlockingHit ? FColor::Blue : FColor::Red,
|
||
false, 5.0f, 0, 1.0f);
|
||
}
|
||
|
||
if (Hit.bBlockingHit == true) {
|
||
// Check if this particular hit isn't against a wall (>CoverMaxHeight)
|
||
|
||
FHitResult SphereHit;
|
||
FVector SpherePosition = Hit.ImpactPoint +
|
||
FVector(
|
||
0.0f, 0.0f,
|
||
CoverObstacleMaxHeight + CoverAimWindowRadius - CoverObstacleMinHeight);
|
||
|
||
GetWorld()->SweepSingleByChannel(
|
||
SphereHit,
|
||
SpherePosition,
|
||
SpherePosition,
|
||
FQuat::Identity,
|
||
CoverTraceChannel,
|
||
FCollisionShape::MakeSphere(CoverAimWindowRadius),
|
||
QueryParams);
|
||
|
||
if (bShowCoverSystemDebug)
|
||
{
|
||
DrawDebugSphere(GetWorld(), SphereHit.TraceStart, CoverAimWindowRadius, 8,
|
||
Hit.bBlockingHit ? FColor::Blue : FColor::Red,
|
||
false, 5.0f, 0, 1);
|
||
}
|
||
|
||
if (SphereHit.bBlockingHit == false)
|
||
{
|
||
ValidHits.Add(Hit);
|
||
}
|
||
}
|
||
|
||
|
||
// Rotate trace to search in another direction next time
|
||
TraceDirection = TraceDirection.RotateAngleAxis(45, FVector(0, 0, 1));
|
||
//UE_LOG(LogTemp, Display, TEXT("Check Cover %d"), i);
|
||
}
|
||
|
||
return ValidHits.Num() > 0;
|
||
}
|
||
|
||
void AExoPlayerController::AdjustCameraWhileInCover()
|
||
{
|
||
if (!bIsInCover)
|
||
{
|
||
return;
|
||
}
|
||
|
||
MaxCoverAimHeight = PlayerCharacter->StandingEyeHeight * CoverAimStandFactor;
|
||
|
||
//FVector StartOffset = GetCharacter()->GetActorForwardVector() * 20.0f;
|
||
FVector ObstacleTraceStart = GetCharacter()->GetActorLocation();
|
||
ObstacleTraceStart.Z += GetCharacter()->CrouchedEyeHeight;
|
||
FVector ObstacleTraceEnd =
|
||
ObstacleTraceStart + PlayerCameraManager->GetCameraRotation().Vector() * CoverAimFreeDistance;
|
||
FCollisionQueryParams QueryParams;
|
||
QueryParams.AddIgnoredActor(PlayerCharacter);
|
||
TEnumAsByte<ECollisionChannel> ObstacleTraceChannel = ECC_WorldStatic;
|
||
|
||
FHitResult Hit;
|
||
bool bFreeSpace = false;
|
||
|
||
// Trace until no hit is found (free space to aim)
|
||
for (int i = 0; i < MaxCoverAimHeight; i += 2*CoverAimWindowRadius)
|
||
{
|
||
ObstacleTraceStart.Z += i;
|
||
ObstacleTraceEnd.Z += i;
|
||
|
||
GetWorld()->SweepSingleByChannel(
|
||
Hit,
|
||
ObstacleTraceStart,
|
||
ObstacleTraceEnd,
|
||
FQuat::Identity,
|
||
ObstacleTraceChannel,
|
||
FCollisionShape::MakeSphere(CoverAimWindowRadius),
|
||
QueryParams
|
||
);
|
||
|
||
if (bShowCoverSystemDebug)
|
||
{
|
||
DrawDebugSphere(GetWorld(), Hit.Location, CoverAimWindowRadius, 8,
|
||
Hit.bBlockingHit ? FColor::Blue : FColor::Red,
|
||
false, 0.0f, 0, 0);
|
||
}
|
||
|
||
if (Hit.bBlockingHit == false)
|
||
{
|
||
UE_LOG(LogTemp, Display, TEXT("Free space"));
|
||
bFreeSpace = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (bFreeSpace)
|
||
{
|
||
TargetCoverStandAlpha = FMath::GetMappedRangeValueClamped(
|
||
UE::Math::TVector2<float>(GetCharacter()->CrouchedEyeHeight, GetCharacter()->CrouchedEyeHeight + MaxCoverAimHeight),
|
||
UE::Math::TVector2(0.0f, 1.0f),
|
||
Hit.TraceStart.Z
|
||
);
|
||
if (bShowCoverSystemDebug)
|
||
{
|
||
DrawDebugLine(GetWorld(), Hit.TraceStart, Hit.TraceEnd, FColor::Red);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// IDEA FOR A MORE COMPLEX SYSTEM IN THE FUTURE:
|
||
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
||
// To not introduce accidental flickering and optimize it a bit, all of this should run
|
||
// ONLY on cover update
|
||
|
||
// FVector CrouchedEyeWorldPosition = PlayerCharacter->GetActorLocation();
|
||
// CrouchedEyeWorldPosition.Z += PlayerCharacter->CrouchedEyeHeight;
|
||
// FVector CoverCeiling = CrouchedEyeWorldPosition + MaxCoverAimHeight;
|
||
// float PlayerCapsuleRadius = PlayerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
|
||
// TEnumAsByte<ECollisionChannel> ObstacleTraceChannel = ECC_WorldStatic;
|
||
// FCollisionQueryParams QueryParams;
|
||
// QueryParams.AddIgnoredActor(PlayerCharacter);
|
||
//
|
||
// // Shoot ray up at maximum cover length to determine how high the player can "stand up"
|
||
// FHitResult WallTopHit;
|
||
// GetWorld()->SweepSingleByChannel(
|
||
// WallTopHit,
|
||
// CrouchedEyeWorldPosition,
|
||
// CoverCeiling,
|
||
// FQuat::Identity,
|
||
// ObstacleTraceChannel,
|
||
// FCollisionShape::MakeSphere(PlayerCapsuleRadius),
|
||
// QueryParams
|
||
// );
|
||
//
|
||
// FVector CeilingWorldLocation;
|
||
// if (WallTopHit.bBlockingHit == true)
|
||
// {
|
||
// CeilingWorldLocation = WallTopHit.Location;
|
||
// }
|
||
// else
|
||
// {
|
||
// CeilingWorldLocation = WallTopHit.TraceEnd;
|
||
// }
|
||
//
|
||
// // From hit location (or if no hit - from end position) shoot ray towards camera's forward
|
||
// // vector at minimum cover length
|
||
//
|
||
// constexpr float WallFinderDistance = 75.0f;
|
||
// FVector WallFinderDirection = PlayerCharacter->GetActorForwardVector();
|
||
// WallFinderDirection.Z = 0.0f; // Not needed if the player always stays upright
|
||
//
|
||
// GetWorld()->LineTraceSingleByChannel(
|
||
// WallTopHit,
|
||
// CeilingWorldLocation,
|
||
// CeilingWorldLocation + (WallFinderDirection * WallFinderDistance),
|
||
// ObstacleTraceChannel,
|
||
// QueryParams
|
||
// );
|
||
//
|
||
// // regardless of the hit result shoot multi-ray downwards from end location
|
||
// // with maximum cover length
|
||
//
|
||
// FVector WallTopTraceStart = WallTopHit.bBlockingHit ? WallTopHit.Location : WallTopHit.TraceEnd;
|
||
// TArray<FHitResult> ValidHitsCandidateArray;
|
||
//
|
||
// GetWorld()->LineTraceMultiByChannel(
|
||
// ValidHitsCandidateArray,
|
||
// WallTopTraceStart,
|
||
// WallTopTraceStart + (FVector(0.0f, 0.0f, 0.0f) * MaxCoverAimHeight),
|
||
// ObstacleTraceChannel,
|
||
// QueryParams
|
||
// );
|
||
|
||
// On every ray hit trace an in-place sphere to determine if the aiming spot is valid
|
||
|
||
// All valid aiming spots are collected and the lowest (smallest Z) is chosen
|
||
|
||
// Sphere trace from camera forward a certain distance to regulate camera position while
|
||
// aiming up and down // TODO: elaborate
|
||
}
|
||
|
||
void AExoPlayerController::UpdateCoverStandHeight(float DeltaTime)
|
||
{
|
||
// if (FMath::IsNearlyEqual(CoverStandAlpha, TargetCoverStandAlpha, 0.01f))
|
||
// {
|
||
// return;
|
||
// }
|
||
|
||
CoverStandAlpha = FMath::Lerp(CoverStandAlpha, TargetCoverStandAlpha, DeltaTime * 5.0f);
|
||
//CoverStandAlpha = TargetCoverStandAlpha;
|
||
|
||
// BANDAID SOLUTION
|
||
const float NewZ = FMath::Lerp(
|
||
PlayerCharacter->CrouchedEyeHeight,
|
||
PlayerCharacter->StandingEyeHeight,
|
||
CoverStandAlpha
|
||
);
|
||
|
||
PlayerCharacter->CameraComponent->SetRelativeLocation(
|
||
FVector(0.0f, 0.0f, NewZ)
|
||
);
|
||
|
||
if (bShowCoverSystemDebug && GEngine)
|
||
{
|
||
GEngine->AddOnScreenDebugMessage(1, -1, FColor::Red, FString::Printf(TEXT("Current cover stand alpha: %f"), CoverStandAlpha));
|
||
}
|
||
}
|
||
|
||
void AExoPlayerController::OnCoverTimer()
|
||
{
|
||
bIsInCover = CheckForCover();
|
||
if (bIsInCover == false)
|
||
{
|
||
TargetCoverStandAlpha = 0.0f;
|
||
UpdateCoverStandHeight(GetWorld()->GetDeltaSeconds());
|
||
}
|
||
}
|
||
|
||
void AExoPlayerController::DebugCoverSystem(bool show)
|
||
{
|
||
bShowCoverSystemDebug = show;
|
||
}
|