D1/Source/D1/CombatCharacter.cpp

632 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CombatCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Interface/Interact.h"
#include "Actor/BaseWeapon.h"
#include "Components/CombatComponent.h"
#include "Engine/DamageEvents.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
//////////////////////////////////////////////////////////////////////////
// ACombatCharacter
ACombatCharacter::ACombatCharacter()
{
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// Don't rotate when the controller rotates. Let that just affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// Configure character movement
GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // ...at this rotation rate
// Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
// instead of recompiling to adjust them
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
GetCharacterMovement()->GravityScale = 1.75f;
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
// Setting CombatComponent
CombatComponent = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));
// Setting StateManagerComponent
StateManagerComponent = CreateDefaultSubobject<UStateManagerComponent>(TEXT("StateManagerComponent"));
StateManagerComponent->OnStateBegin.AddUObject(this, &ACombatCharacter::CharacterStateBegin);
StateManagerComponent->OnStateEnd.AddUObject(this, &ACombatCharacter::CharacterStateEnd);
StateManagerComponent->OnActionBegin.AddUObject(this, &ACombatCharacter::CharacterActionBegin);
StateManagerComponent->OnActionEnd.AddUObject(this, &ACombatCharacter::CharacterActionEnd);
Health = 100.f;
PelvisBoneName = TEXT("pelvis");
}
void ACombatCharacter::BeginPlay()
{
// Call the base class
Super::BeginPlay();
//Add Input Mapping Context
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
//Player<65><72> <20><><EFBFBD>⸦ ó<><C3B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ְ<EFBFBD><D6B0><EFBFBD>. <20>Ƹ<EFBFBD><C6B8><EFBFBD> <20>ӽ<EFBFBD><D3BD>ڵ<EFBFBD>
FActorSpawnParameters spawnParam;
spawnParam.Owner = this;
spawnParam.Instigator = this;
ABaseEquippable* SpawnItem = Cast<ABaseEquippable>(GetWorld()->SpawnActor(Weapon, &GetActorTransform(), spawnParam));
if (SpawnItem)
SpawnItem->OnEquipped();
}
float ACombatCharacter::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float fDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
{
const FPointDamageEvent* PointDamageEvent = static_cast<const FPointDamageEvent*>(&DamageEvent);
CharacterTakeDamage(fDamage);
//Play Sound
UGameplayStatics::PlaySoundAtLocation(this, HitSound, PointDamageEvent->HitInfo.Location);
//Hit Effect
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), HitEmitter, PointDamageEvent->HitInfo.Location);
if (CanReceiveHitReaction())
{
StateManagerComponent->SetCurrentState(ECharacterState::Disable);
//Play Animation
PlayAnimMontage(HitMontage);
}
}
return fDamage;
}
void ACombatCharacter::ContinueAttack_Implementation()
{
if (CombatComponent->GetIsAttackSaved())
{
CombatComponent->SetIsAttackSaved(false);
if (StateManagerComponent->GetCurrentState() == ECharacterState::Attacking)
StateManagerComponent->SetCurrentState(ECharacterState::Nothing);
AttackEvent();
}
}
void ACombatCharacter::ResetAttack_Implementation()
{
CombatComponent->ResetAttack();
}
void ACombatCharacter::ResetCombat_Implementation()
{
CombatComponent->ResetAttack();
StateManagerComponent->ResetState();
StateManagerComponent->SetCurrentAction(ECharacterAction::Nothing);
}
FRotator ACombatCharacter::GetDesiredRotation_Implementation()
{
FVector InputVector = GetCharacterMovement()->GetLastInputVector();
if (InputVector.Equals(FVector(0, 0, 0), 0.001f))
return GetActorRotation();
else
return UKismetMathLibrary::MakeRotFromX(GetLastMovementInputVector());
}
bool ACombatCharacter::CanReceiveDamage_Implementation()
{
bool result;
result = (StateManagerComponent->GetCurrentState() != ECharacterState::Dead);
return result;
}
//////////////////////////////////////////////////////////////////////////
// Input
void ACombatCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
//Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Jumping);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
//Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Move);
//Looking
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ACombatCharacter::Look);
//Interact
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ACombatCharacter::Interact);
//ToggleCombat
EnhancedInputComponent->BindAction(ToggleCombatAction, ETriggerEvent::Started, this, &ACombatCharacter::ToggleCombat);
//LightAttack
EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::LightAttack);
//Dodge
EnhancedInputComponent->BindAction(DodgeAction, ETriggerEvent::Started, this, &ACombatCharacter::Dodge);
}
}
void ACombatCharacter::Move(const FInputActionValue& Value)
{
// input is a Vector2D
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
void ACombatCharacter::Look(const FInputActionValue& Value)
{
// input is a Vector2D
FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// add yaw and pitch input to controller
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
void ACombatCharacter::Jumping(const FInputActionValue& Value)
{
if (!CanJumping())
return;
StopAnimMontage();
StateManagerComponent->ResetState();
CombatComponent->ResetAttack();
Super::Jump();
}
void ACombatCharacter::Interact(const FInputActionValue& Value)
{
bool bInput = Value.Get<bool>();
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
TEnumAsByte<EObjectTypeQuery> InteractiveType = UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_GameTraceChannel1);
ObjectTypes.Add(InteractiveType);
TArray<AActor*> ActorsToIgnore;
FHitResult OutHit;
if (UKismetSystemLibrary::SphereTraceSingleForObjects(GetWorld(), GetActorLocation(), GetActorLocation(), 100.f, ObjectTypes, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, OutHit, true))
{
IInteract* interactObject = Cast<IInteract>(OutHit.GetActor());
if (interactObject)
interactObject->Interact(this);
}
}
void ACombatCharacter::ToggleCombat(const FInputActionValue& Value)
{
bool bInput = Value.Get<bool>();
ToggleCombatEvent();
}
void ACombatCharacter::LightAttack(const FInputActionValue& Value)
{
if (StateManagerComponent->GetCurrentState() == ECharacterState::Attacking)
CombatComponent->SetIsAttackSaved(true);
else
AttackEvent();
}
void ACombatCharacter::Dodge(const FInputActionValue& Value)
{
if (CanPerformDodge())
PerformDodge();
}
void ACombatCharacter::CharacterStateBegin(ECharacterState CharState)
{
switch (CharState)
{
case ECharacterState::Nothing:
break;
case ECharacterState::Attacking:
break;
case ECharacterState::Dodging:
break;
case ECharacterState::GeneralActionState:
break;
case ECharacterState::Dead:
PerformDeath();
break;
case ECharacterState::Disable:
break;
default:
break;
}
}
void ACombatCharacter::CharacterStateEnd(ECharacterState CharState)
{
switch (CharState)
{
case ECharacterState::Nothing:
break;
case ECharacterState::Attacking:
break;
case ECharacterState::Dodging:
break;
case ECharacterState::GeneralActionState:
break;
case ECharacterState::Dead:
break;
case ECharacterState::Disable:
break;
default:
break;
}
}
void ACombatCharacter::CharacterActionBegin(ECharacterAction CharAction)
{
switch (CharAction)
{
case ECharacterAction::Nothing:
break;
case ECharacterAction::GeneralAction:
break;
case ECharacterAction::LightAttack:
break;
case ECharacterAction::HeavyAttack:
break;
case ECharacterAction::ChargedAttack:
break;
case ECharacterAction::FallingAttack:
break;
case ECharacterAction::SprintAttack:
break;
case ECharacterAction::Dodge:
break;
case ECharacterAction::EnterCombat:
break;
case ECharacterAction::ExitCombat:
break;
default:
break;
}
}
void ACombatCharacter::CharacterActionEnd(ECharacterAction CharAction)
{
switch (CharAction)
{
case ECharacterAction::Nothing:
break;
case ECharacterAction::GeneralAction:
break;
case ECharacterAction::LightAttack:
break;
case ECharacterAction::HeavyAttack:
break;
case ECharacterAction::ChargedAttack:
break;
case ECharacterAction::FallingAttack:
break;
case ECharacterAction::SprintAttack:
break;
case ECharacterAction::Dodge:
break;
case ECharacterAction::EnterCombat:
break;
case ECharacterAction::ExitCombat:
break;
default:
break;
}
}
void ACombatCharacter::ToggleCombatEvent()
{
ABaseWeapon* baseWeapon = CombatComponent->GetMainWeapon();
if (!baseWeapon)
return;
if (!CanPerformToggleCombat())
return;
if (!CombatComponent->GetCombatEnabled())
PerformAction(ECharacterState::GeneralActionState, ECharacterAction::EnterCombat, 0);
else
PerformAction(ECharacterState::GeneralActionState, ECharacterAction::ExitCombat, 0);
}
void ACombatCharacter::AttackEvent()
{
if (!CanPerformAttack())
return;
if (CombatComponent->GetCombatEnabled())
PerformAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount());
else
ToggleCombatEvent();
}
void ACombatCharacter::CharacterTakeDamage(float InDamage)
{
float tmp = Health - InDamage;
Health = UKismetMathLibrary::Clamp(tmp, 0, Health);
if (Health <= 0)
StateManagerComponent->SetCurrentState(ECharacterState::Dead);
}
void ACombatCharacter::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
{
if (!GetMesh())
return;
GetMesh()->SetPhysicsLinearVelocity(GetActorForwardVector() * -InitialSpeed, false, PelvisBoneName);
}
void ACombatCharacter::EnableRagdoll()
{
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None, 0); //movement <20><> <20>̻<EFBFBD> <20><><EFBFBD><EFBFBD> <20>ٲ<EFBFBD>
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore); //<2F><EFBFBD><E6B5B9><EFBFBD><EFBFBD>
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); //ī<>޶<EFBFBD> <20><EFBFBD><E6B5B9><EFBFBD><EFBFBD>
FAttachmentTransformRules rules(EAttachmentRule::KeepWorld, EAttachmentRule::KeepWorld, EAttachmentRule::KeepWorld, true);
GetCameraBoom()->AttachToComponent(GetMesh(), rules, PelvisBoneName); //camera<72><61> ô<>߻<EFBFBD><DFBB><EFBFBD> <20><><EFBFBD><EFBFBD>
GetCameraBoom()->bDoCollisionTest = false; //ī<>޶<EFBFBD> <20><20><><EFBFBD><EFBFBD> <20><>
GetMesh()->SetCollisionProfileName(TEXT("ragdoll"), true); //ragdoll <20><> <20><><EFBFBD><EFBFBD>
GetMesh()->SetAllBodiesBelowSimulatePhysics(PelvisBoneName, true, true);
GetMesh()->SetAllBodiesBelowPhysicsBlendWeight(PelvisBoneName, 1.f);
}
void ACombatCharacter::PerformAttack(ECharacterAction attackType, int32 attackIndex)
{
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(attackType);
int32 attackIdx = attackIndex;
if (montages.Num() <= attackIdx)
attackIdx = 0;
if (!montages.IsValidIndex(attackIdx))
return;
UAnimMontage* attackMontage = montages[attackIdx];
if (!IsValid(attackMontage))
{
FString debugStr = FString::Printf(TEXT("Index %d Is NOT VALID!!"), attackIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr);
return;
}
else
{
StateManagerComponent->SetCurrentState(ECharacterState::Attacking);
StateManagerComponent->SetCurrentAction(attackType);
PlayAnimMontage(attackMontage);
int idx = attackIdx + 1;
if (idx > montages.Num())
idx = 0;
CombatComponent->SetAttackCount(idx);
}
}
//<2F><><EFBFBD>ڰ<EFBFBD><DAB0><EFBFBD> <20><><EFBFBD>߿<EFBFBD> <20>ʿ<EFBFBD><CABF>ϸ<EFBFBD> <20>߰<EFBFBD> PerformDodge(int32 dodgeIndex)
void ACombatCharacter::PerformDodge()
{
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return;
int32 montageIndex = 0;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(ECharacterAction::Dodge);
if (montages.Num() <= montageIndex)
montageIndex = 0;
if (!montages.IsValidIndex(montageIndex))
return;
UAnimMontage* dodgeMontage = montages[montageIndex];
if (IsValid(dodgeMontage))
{
StateManagerComponent->SetCurrentState(ECharacterState::Dodging);
StateManagerComponent->SetCurrentAction(ECharacterAction::Dodge);
PlayAnimMontage(dodgeMontage);
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIndex);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
}
}
bool ACombatCharacter::PerformAction(ECharacterState characterState, ECharacterAction characterAction, int32 montageIndex)
{
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return false;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(characterAction);
int32 montageIdx = montageIndex;
if (montages.Num() <= montageIdx)
montageIdx = 0;
if (!montages.IsValidIndex(montageIdx))
return false;
UAnimMontage* actionMontage = montages[montageIdx];
if (IsValid(actionMontage))
{
StateManagerComponent->SetCurrentState(characterState);
StateManagerComponent->SetCurrentAction(characterAction);
PlayAnimMontage(actionMontage);
return true;
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
return false;
}
}
void ACombatCharacter::PerformDeath()
{
EnableRagdoll();
ApplyHitReactionPhysicsVelocity(2000.f); //<2F><EFBFBD><E6B5B9> <20><> <20><> <20>׷<EFBFBD><D7B7><EFBFBD><EFBFBD>ϰ<EFBFBD> <20>ϱ<EFBFBD> <20><><EFBFBD>ؼ<EFBFBD> <20>޹<EFBFBD><DEB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
if (IsValid(CombatComponent->GetMainWeapon()))
CombatComponent->GetMainWeapon()->SimulateWeaponPhysics(); //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>浹Ŵ
FTimerHandle deathTimer;
GetWorld()->GetTimerManager().SetTimer(deathTimer, FTimerDelegate::CreateLambda([&]()
{
if (IsValid(CombatComponent->GetMainWeapon()))
CombatComponent->GetMainWeapon()->Destroy();
this->Destroy();
}), 4.f, false); // 4<><34> <20>Ŀ<EFBFBD> object <20><><EFBFBD><EFBFBD>
}
bool ACombatCharacter::CanPerformToggleCombat()
{
bool ReturnValue = true;
TArray<ECharacterState> inputArr = {
ECharacterState::Attacking,
ECharacterState::Dodging,
ECharacterState::Dead,
ECharacterState::Disable,
ECharacterState::GeneralActionState
};
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr);
ReturnValue &= !GetCharacterMovement()->IsFalling();
return ReturnValue;
}
bool ACombatCharacter::CanPerformAttack()
{
bool ReturnValue = true;
TArray<ECharacterState> inputArr = {
ECharacterState::Attacking,
ECharacterState::Dodging,
ECharacterState::Dead,
ECharacterState::Disable,
ECharacterState::GeneralActionState
};
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr);
return ReturnValue;
}
bool ACombatCharacter::CanPerformDodge()
{
bool ReturnValue = true;
TArray<ECharacterState> inputArr = {
ECharacterState::Dodging,
ECharacterState::Dead,
ECharacterState::Disable,
ECharacterState::GeneralActionState
};
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr);
ReturnValue &= !GetCharacterMovement()->IsFalling();
return ReturnValue;
}
bool ACombatCharacter::CanJumping()
{
bool ReturnValue = true;
TArray<ECharacterState> inputArr = {
ECharacterState::Dodging,
ECharacterState::Dead,
ECharacterState::Disable,
ECharacterState::GeneralActionState
};
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr);
ReturnValue &= !GetCharacterMovement()->IsFalling();
return ReturnValue;
}
bool ACombatCharacter::CanReceiveHitReaction()
{
bool ReturnValue = true;
TArray<ECharacterState> inputArr = {
ECharacterState::Dead,
};
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr);
return ReturnValue;
}
ECharacterAction ACombatCharacter::GetDesiredAttackType()
{
if (GetCharacterMovement()->IsFalling())
return ECharacterAction::FallingAttack;
//TODO : Movement Speed Mode <20>߰<EFBFBD>
//TODO : Heavy Attack <20>߰<EFBFBD>
return ECharacterAction::LightAttack;
}