// 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(GetPawn()); check(PlayerCharacter); UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); if (Subsystem) { Subsystem->AddMappingContext(InputContext, 0); } //InteractionComponent = PlayerCharacter->FindComponentByClass(); //ShootingComponent = PlayerCharacter->FindComponentByClass(); InteractionComponent = PlayerCharacter->InteractionComponent; ShootingComponent = PlayerCharacter->ShootingComponent; // Ustawianie w komponencie poruszania pr�dko�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(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(); 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(); 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 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 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(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 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 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; }