|
|
|
|
|
|
| #include "CombatCharacter.h"
|
| #include "Components/CapsuleComponent.h"
|
| #include "Components/WidgetComponent.h"
|
| #include "GameFramework/CharacterMovementComponent.h"
|
| #include "GameFramework/SpringArmComponent.h"
|
| #include "Components/SkeletalMeshComponent.h"
|
| #include "Camera/CameraComponent.h"
|
| #include "EnhancedInputSubsystems.h"
|
| #include "EnhancedInputComponent.h"
|
| #include "CombatLifeBar.h"
|
| #include "Engine/DamageEvents.h"
|
| #include "TimerManager.h"
|
| #include "Engine/LocalPlayer.h"
|
| #include "CombatPlayerController.h"
|
|
|
| ACombatCharacter::ACombatCharacter()
|
| {
|
| PrimaryActorTick.bCanEverTick = true;
|
|
|
|
|
| OnAttackMontageEnded.BindUObject(this, &ACombatCharacter::AttackMontageEnded);
|
|
|
|
|
| GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
|
|
|
|
|
| GetCharacterMovement()->MaxWalkSpeed = 400.0f;
|
|
|
|
|
| CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
| CameraBoom->SetupAttachment(RootComponent);
|
|
|
| CameraBoom->TargetArmLength = DefaultCameraDistance;
|
| CameraBoom->bUsePawnControlRotation = true;
|
| CameraBoom->bEnableCameraLag = true;
|
| CameraBoom->bEnableCameraRotationLag = true;
|
|
|
|
|
| FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
|
| FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
| FollowCamera->bUsePawnControlRotation = false;
|
|
|
|
|
| LifeBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("LifeBar"));
|
| LifeBar->SetupAttachment(RootComponent);
|
|
|
|
|
| Tags.Add(FName("Player"));
|
| }
|
|
|
| void ACombatCharacter::Move(const FInputActionValue& Value)
|
| {
|
|
|
| FVector2D MovementVector = Value.Get<FVector2D>();
|
|
|
|
|
| DoMove(MovementVector.X, MovementVector.Y);
|
| }
|
|
|
| void ACombatCharacter::Look(const FInputActionValue& Value)
|
| {
|
| FVector2D LookAxisVector = Value.Get<FVector2D>();
|
|
|
|
|
| DoLook(LookAxisVector.X, LookAxisVector.Y);
|
| }
|
|
|
| void ACombatCharacter::ComboAttackPressed()
|
| {
|
|
|
| DoComboAttackStart();
|
| }
|
|
|
| void ACombatCharacter::ChargedAttackPressed()
|
| {
|
|
|
| DoChargedAttackStart();
|
| }
|
|
|
| void ACombatCharacter::ChargedAttackReleased()
|
| {
|
|
|
| DoChargedAttackEnd();
|
| }
|
|
|
| void ACombatCharacter::ToggleCamera()
|
| {
|
|
|
| BP_ToggleCamera();
|
| }
|
|
|
| void ACombatCharacter::DoMove(float Right, float Forward)
|
| {
|
| if (GetController() != nullptr)
|
| {
|
|
|
| const FRotator Rotation = GetController()->GetControlRotation();
|
| const FRotator YawRotation(0, Rotation.Yaw, 0);
|
|
|
|
|
| const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
|
|
|
|
| const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
|
|
|
|
| AddMovementInput(ForwardDirection, Forward);
|
| AddMovementInput(RightDirection, Right);
|
| }
|
| }
|
|
|
| void ACombatCharacter::DoLook(float Yaw, float Pitch)
|
| {
|
| if (GetController() != nullptr)
|
| {
|
|
|
| AddControllerYawInput(Yaw);
|
| AddControllerPitchInput(Pitch);
|
| }
|
| }
|
|
|
| void ACombatCharacter::DoComboAttackStart()
|
| {
|
|
|
| if (bIsAttacking)
|
| {
|
|
|
| CachedAttackInputTime = GetWorld()->GetTimeSeconds();
|
|
|
| return;
|
| }
|
|
|
|
|
| ComboAttack();
|
| }
|
|
|
| void ACombatCharacter::DoComboAttackEnd()
|
| {
|
|
|
| }
|
|
|
| void ACombatCharacter::DoChargedAttackStart()
|
| {
|
|
|
| bIsChargingAttack = true;
|
|
|
| if (bIsAttacking)
|
| {
|
|
|
| CachedAttackInputTime = GetWorld()->GetTimeSeconds();
|
|
|
| return;
|
| }
|
|
|
| ChargedAttack();
|
| }
|
|
|
| void ACombatCharacter::DoChargedAttackEnd()
|
| {
|
|
|
| bIsChargingAttack = false;
|
|
|
|
|
| if (bHasLoopedChargedAttack)
|
| {
|
| CheckChargedAttack();
|
| }
|
| }
|
|
|
| void ACombatCharacter::ResetHP()
|
| {
|
|
|
| CurrentHP = MaxHP;
|
|
|
|
|
| LifeBarWidget->SetLifePercentage(1.0f);
|
| }
|
|
|
| void ACombatCharacter::ComboAttack()
|
| {
|
|
|
| bIsAttacking = true;
|
|
|
|
|
| ComboCount = 0;
|
|
|
|
|
| NotifyEnemiesOfIncomingAttack();
|
|
|
|
|
| if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
|
| {
|
| const float MontageLength = AnimInstance->Montage_Play(ComboAttackMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true);
|
|
|
|
|
| if (MontageLength > 0.0f)
|
| {
|
|
|
| AnimInstance->Montage_SetEndDelegate(OnAttackMontageEnded, ComboAttackMontage);
|
| }
|
| }
|
|
|
| }
|
|
|
| void ACombatCharacter::ChargedAttack()
|
| {
|
|
|
| bIsAttacking = true;
|
|
|
|
|
| bHasLoopedChargedAttack = false;
|
|
|
|
|
| NotifyEnemiesOfIncomingAttack();
|
|
|
|
|
| if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
|
| {
|
| const float MontageLength = AnimInstance->Montage_Play(ChargedAttackMontage, 1.0f, EMontagePlayReturnType::MontageLength, 0.0f, true);
|
|
|
|
|
| if (MontageLength > 0.0f)
|
| {
|
|
|
| AnimInstance->Montage_SetEndDelegate(OnAttackMontageEnded, ChargedAttackMontage);
|
| }
|
| }
|
| }
|
|
|
| void ACombatCharacter::AttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
|
| {
|
|
|
| bIsAttacking = false;
|
|
|
|
|
| if (GetWorld()->GetTimeSeconds() - CachedAttackInputTime <= AttackInputCacheTimeTolerance)
|
| {
|
|
|
| if (bIsChargingAttack)
|
| {
|
|
|
| ChargedAttack();
|
| }
|
| else
|
| {
|
|
|
| ComboAttack();
|
| }
|
| }
|
| }
|
|
|
| void ACombatCharacter::DoAttackTrace(FName DamageSourceBone)
|
| {
|
|
|
| TArray<FHitResult> OutHits;
|
|
|
|
|
| const FVector TraceStart = GetMesh()->GetSocketLocation(DamageSourceBone);
|
| const FVector TraceEnd = TraceStart + (GetActorForwardVector() * MeleeTraceDistance);
|
|
|
|
|
| FCollisionObjectQueryParams ObjectParams;
|
| ObjectParams.AddObjectTypesToQuery(ECC_Pawn);
|
| ObjectParams.AddObjectTypesToQuery(ECC_WorldDynamic);
|
|
|
|
|
| FCollisionShape CollisionShape;
|
| CollisionShape.SetSphere(MeleeTraceRadius);
|
|
|
|
|
| FCollisionQueryParams QueryParams;
|
| QueryParams.AddIgnoredActor(this);
|
|
|
| if (GetWorld()->SweepMultiByObjectType(OutHits, TraceStart, TraceEnd, FQuat::Identity, ObjectParams, CollisionShape, QueryParams))
|
| {
|
|
|
| for (const FHitResult& CurrentHit : OutHits)
|
| {
|
|
|
| ICombatDamageable* Damageable = Cast<ICombatDamageable>(CurrentHit.GetActor());
|
|
|
| if (Damageable)
|
| {
|
|
|
| const FVector Impulse = (CurrentHit.ImpactNormal * -MeleeKnockbackImpulse) + (FVector::UpVector * MeleeLaunchImpulse);
|
|
|
|
|
| Damageable->ApplyDamage(MeleeDamage, this, CurrentHit.ImpactPoint, Impulse);
|
|
|
|
|
| DealtDamage(MeleeDamage, CurrentHit.ImpactPoint);
|
| }
|
| }
|
| }
|
| }
|
|
|
| void ACombatCharacter::CheckCombo()
|
| {
|
|
|
| if (bIsAttacking && !bIsChargingAttack)
|
| {
|
|
|
| if (GetWorld()->GetTimeSeconds() - CachedAttackInputTime <= ComboInputCacheTimeTolerance)
|
| {
|
|
|
| CachedAttackInputTime = 0.0f;
|
|
|
|
|
| ++ComboCount;
|
|
|
|
|
| if (ComboCount < ComboSectionNames.Num())
|
| {
|
|
|
| NotifyEnemiesOfIncomingAttack();
|
|
|
|
|
| if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
|
| {
|
| AnimInstance->Montage_JumpToSection(ComboSectionNames[ComboCount], ComboAttackMontage);
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| void ACombatCharacter::CheckChargedAttack()
|
| {
|
|
|
| bHasLoopedChargedAttack = true;
|
|
|
|
|
| if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
|
| {
|
| AnimInstance->Montage_JumpToSection(bIsChargingAttack ? ChargeLoopSection : ChargeAttackSection, ChargedAttackMontage);
|
| }
|
| }
|
|
|
| void ACombatCharacter::NotifyEnemiesOfIncomingAttack()
|
| {
|
|
|
| TArray<FHitResult> OutHits;
|
|
|
|
|
| const FVector TraceStart = GetActorLocation();
|
| const FVector TraceEnd = TraceStart + (GetActorForwardVector() * DangerTraceDistance);
|
|
|
|
|
| FCollisionObjectQueryParams ObjectParams;
|
| ObjectParams.AddObjectTypesToQuery(ECC_Pawn);
|
|
|
|
|
| FCollisionShape CollisionShape;
|
| CollisionShape.SetSphere(DangerTraceRadius);
|
|
|
|
|
| FCollisionQueryParams QueryParams;
|
| QueryParams.AddIgnoredActor(this);
|
|
|
| if (GetWorld()->SweepMultiByObjectType(OutHits, TraceStart, TraceEnd, FQuat::Identity, ObjectParams, CollisionShape, QueryParams))
|
| {
|
|
|
| for (const FHitResult& CurrentHit : OutHits)
|
| {
|
|
|
| ICombatDamageable* Damageable = Cast<ICombatDamageable>(CurrentHit.GetActor());
|
|
|
| if (Damageable)
|
| {
|
|
|
| Damageable->NotifyDanger(GetActorLocation(), this);
|
| }
|
| }
|
| }
|
| }
|
|
|
| void ACombatCharacter::ApplyDamage(float Damage, AActor* DamageCauser, const FVector& DamageLocation, const FVector& DamageImpulse)
|
| {
|
|
|
| FDamageEvent DamageEvent;
|
| const float ActualDamage = TakeDamage(Damage, DamageEvent, nullptr, DamageCauser);
|
|
|
|
|
| if (ActualDamage > 0.0f)
|
| {
|
|
|
| GetCharacterMovement()->AddImpulse(DamageImpulse, true);
|
|
|
|
|
| if (GetMesh()->IsSimulatingPhysics())
|
| {
|
|
|
| GetMesh()->AddImpulseAtLocation(DamageImpulse * GetMesh()->GetMass(), DamageLocation);
|
| }
|
|
|
|
|
| ReceivedDamage(ActualDamage, DamageLocation, DamageImpulse.GetSafeNormal());
|
| }
|
|
|
| }
|
|
|
| void ACombatCharacter::HandleDeath()
|
| {
|
|
|
| GetCharacterMovement()->DisableMovement();
|
|
|
|
|
| GetMesh()->SetSimulatePhysics(true);
|
|
|
|
|
| LifeBar->SetHiddenInGame(true);
|
|
|
|
|
| GetCameraBoom()->TargetArmLength = DeathCameraDistance;
|
|
|
|
|
| GetWorld()->GetTimerManager().SetTimer(RespawnTimer, this, &ACombatCharacter::RespawnCharacter, RespawnTime, false);
|
| }
|
|
|
| void ACombatCharacter::ApplyHealing(float Healing, AActor* Healer)
|
| {
|
|
|
| }
|
|
|
| void ACombatCharacter::NotifyDanger(const FVector& DangerLocation, AActor* DangerSource)
|
| {
|
|
|
| }
|
|
|
| void ACombatCharacter::RespawnCharacter()
|
| {
|
|
|
| Destroy();
|
| }
|
|
|
| float ACombatCharacter::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
|
| {
|
|
|
| if (CurrentHP <= 0.0f)
|
| {
|
| return 0.0f;
|
| }
|
|
|
|
|
| CurrentHP -= Damage;
|
|
|
|
|
| if (CurrentHP <= 0.0f)
|
| {
|
|
|
| HandleDeath();
|
| }
|
| else
|
| {
|
|
|
| LifeBarWidget->SetLifePercentage(CurrentHP / MaxHP);
|
|
|
|
|
| GetMesh()->SetPhysicsBlendWeight(0.5f);
|
| GetMesh()->SetBodySimulatePhysics(PelvisBoneName, false);
|
| }
|
|
|
|
|
| return Damage;
|
| }
|
|
|
| void ACombatCharacter::Landed(const FHitResult& Hit)
|
| {
|
| Super::Landed(Hit);
|
|
|
|
|
| if (CurrentHP >= 0.0f)
|
| {
|
|
|
| GetMesh()->SetPhysicsBlendWeight(0.0f);
|
| }
|
| }
|
|
|
| void ACombatCharacter::BeginPlay()
|
| {
|
| Super::BeginPlay();
|
|
|
|
|
| LifeBarWidget = Cast<UCombatLifeBar>(LifeBar->GetUserWidgetObject());
|
| check(LifeBarWidget);
|
|
|
|
|
| GetCameraBoom()->TargetArmLength = DefaultCameraDistance;
|
|
|
|
|
| MeshStartingTransform = GetMesh()->GetRelativeTransform();
|
|
|
|
|
| LifeBarWidget->SetBarColor(LifeBarColor);
|
|
|
|
|
| ResetHP();
|
| }
|
|
|
| void ACombatCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
| {
|
| Super::EndPlay(EndPlayReason);
|
|
|
|
|
| GetWorld()->GetTimerManager().ClearTimer(RespawnTimer);
|
| }
|
|
|
| void ACombatCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
| {
|
| Super::SetupPlayerInputComponent(PlayerInputComponent);
|
|
|
|
|
| if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
|
| {
|
|
|
| EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Move);
|
|
|
|
|
| EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Look);
|
| EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Look);
|
|
|
|
|
| EnhancedInputComponent->BindAction(ComboAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::ComboAttackPressed);
|
|
|
|
|
| EnhancedInputComponent->BindAction(ChargedAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::ChargedAttackPressed);
|
| EnhancedInputComponent->BindAction(ChargedAttackAction, ETriggerEvent::Completed, this, &ACombatCharacter::ChargedAttackReleased);
|
|
|
|
|
| EnhancedInputComponent->BindAction(ToggleCameraAction, ETriggerEvent::Triggered, this, &ACombatCharacter::ToggleCamera);
|
| }
|
| }
|
|
|
| void ACombatCharacter::NotifyControllerChanged()
|
| {
|
| Super::NotifyControllerChanged();
|
|
|
|
|
| if (ACombatPlayerController* PC = Cast<ACombatPlayerController>(GetController()))
|
| {
|
| PC->SetRespawnTransform(GetActorTransform());
|
| }
|
| }
|
|
|
|
|