[박치영]

- 행동트리 기초작업
- AI 기본 객체 개발
- AI 체력바 UI 추가
main
PCYPC\pcy35 2023-08-27 20:32:54 +09:00
parent 08002c3777
commit 9ef28a708f
28 changed files with 931 additions and 139 deletions

View File

@ -132,4 +132,6 @@ ManualIPAddress=
+FunctionRedirects=(OldName="/Script/D1.CollisionComponent.DisableCollision",NewName="/Script/D1.CollisionComponent.DeactivateCollision")
+PropertyRedirects=(OldName="/Script/D1.BaseDualWeapon.RightFoot",NewName="/Script/D1.BaseDualWeapon.RightFootCollisionComponent")
+PropertyRedirects=(OldName="/Script/D1.CombatCharacter.ToogleLockOnAction",NewName="/Script/D1.CombatCharacter.ToggleLockOnAction")
+ClassRedirects=(OldName="/Script/D1.UI_LockOnComponent",NewName="/Script/D1.LockOnWidgetComponent")
+ClassRedirects=(OldName="/Script/D1.UI_LockOnComponent",NewName="/Script/D1.LockOnWidgetComponent")
+PropertyRedirects=(OldName="/Script/D1.MasterAI.TargetingWidget",NewName="/Script/D1.MasterAI.TargetingWidgetComponent")
+PropertyRedirects=(OldName="/Script/D1.HumanoidEnemy.HealthBar",NewName="/Script/D1.HumanoidEnemy.HealthBarComponent")

Binary file not shown.

Binary file not shown.

View File

@ -10,7 +10,8 @@
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine",
"UMG"
"UMG",
"AIModule"
]
}
],

View File

@ -0,0 +1,22 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/CombatAIController.h"
#include "MasterAI.h"
ACombatAIController::ACombatAIController()
{
}
void ACombatAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
AMasterAI* AIpawn = Cast<AMasterAI>(InPawn);
if(AIpawn)
{
MasterAI = AIpawn;
RunBehaviorTree(MasterAI->GetBeHaviorTree());
}
}

View File

@ -0,0 +1,24 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "CombatAIController.generated.h"
/**
*
*/
UCLASS()
class D1_API ACombatAIController : public AAIController
{
GENERATED_BODY()
public:
ACombatAIController();
protected:
virtual void OnPossess(APawn* InPawn) override;
private:
TObjectPtr<class AMasterAI> MasterAI;
};

View File

@ -0,0 +1,47 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/HumanoidEnemy.h"
#include "Blueprint/UserWidget.h"
#include "Components/WidgetComponent.h"
#include "Kismet/GameplayStatics.h"
#include "UI/UI_HealthBar.h"
AHumanoidEnemy::AHumanoidEnemy()
{
TargetingWidgetComponent->SetRelativeLocation(FVector(0.f, 0.f, 132.f));
HealthBarComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBar"));
static ConstructorHelpers::FClassFinder<UUserWidget> WidgetRef(TEXT("/Game/CombatSystem/UI/WBP_HealthBar.WBP_HealthBar_C"));
if(WidgetRef.Class)
{
HealthBarComponent->SetWidgetClass(WidgetRef.Class);
HealthBarComponent->SetWidgetSpace(EWidgetSpace::Screen);
HealthBarComponent->SetDrawSize({150.f, 10.f});
HealthBarComponent->SetupAttachment(GetMesh());
HealthBarComponent->SetRelativeLocation(FVector(0.f, 0.f, 200.f));
HealthBarComponent->SetVisibility(false);
}
}
void AHumanoidEnemy::BeginPlay()
{
Super::BeginPlay();
if(APlayerController* playerController = UGameplayStatics::GetPlayerController(GetWorld(), 0))
{
UUI_HealthBar* HealthBarRef = Cast<UUI_HealthBar>(HealthBarComponent->GetWidget());
if(HealthBarRef) //ExposeOnSpawn 대용으로 사용
{
HealthBarRef->InitializeHealthBar(this->StatsComponent, EStats::Health);
HealthBarComponent->SetWidget(HealthBarRef);
}
}
}
void AHumanoidEnemy::OnTargeted(bool bIsTargeted)
{
Super::OnTargeted(bIsTargeted);
HealthBarComponent->SetVisibility(bIsTargeted);
}

View File

@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AI/MasterAI.h"
#include "HumanoidEnemy.generated.h"
/**
*
*/
UCLASS()
class D1_API AHumanoidEnemy : public AMasterAI
{
GENERATED_BODY()
public:
AHumanoidEnemy();
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="UI", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UWidgetComponent> HealthBarComponent;
protected:
virtual void BeginPlay() override;
protected:
// Inherited via ITargetingInterface
virtual void OnTargeted(bool bIsTargeted) override;
};

View File

@ -0,0 +1,528 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/MasterAI.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "Actor/BaseWeapon.h"
#include "Components/CombatComponent.h"
#include "Components/StatsComponent.h"
#include "Definitions/CombatGameplayTags.h"
#include "Engine/DamageEvents.h"
#include "NiagaraFunctionLibrary.h"
#include "Components/WidgetComponent.h"
#include "DamageType/AttackDamageType.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
AMasterAI::AMasterAI()
{
// 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()->MaxAcceleration = 1500.f;
GetCharacterMovement()->BrakingFrictionFactor = 1.f;
GetCharacterMovement()->bUseSeparateBrakingFriction = true; //Sprint 시 애니메이션이 끊기는 거 방지
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
GetCharacterMovement()->GravityScale = 1.75f;
GetMesh()->SetRelativeLocationAndRotation(FVector(0.f, 0.f, -100.f), FRotator(0.f, -90.f, 0.f));
//Setting Widget class is Editor to Blueprint
TargetingWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("TargetingWidget"));
static ConstructorHelpers::FClassFinder<UUserWidget> WidgetRef(TEXT("/Game/CombatSystem/UI/WBP_LockOn.WBP_LockOn_C"));
if(WidgetRef.Class)
{
TargetingWidgetComponent->SetWidgetClass(WidgetRef.Class);
TargetingWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
TargetingWidgetComponent->SetDrawSize({20.f, 20.f});
TargetingWidgetComponent->SetupAttachment(GetMesh());
TargetingWidgetComponent->SetRelativeLocation(FVector(0.f, 0.f, 40.f));
TargetingWidgetComponent->SetVisibility(false);
}
// Setting CombatComponent
CombatComponent = CreateDefaultSubobject<UCombatComponent>(TEXT("CombatComponent"));
// Setting StateManagerComponent
StateManagerComponent = CreateDefaultSubobject<UStateManagerComponent>(TEXT("StateManagerComponent"));
StateManagerComponent->OnStateBegin.AddUObject(this, &AMasterAI::CharacterStateBegin);
StateManagerComponent->OnStateEnd.AddUObject(this, &AMasterAI::CharacterStateEnd);
// Setting StatsComponent
StatsComponent = CreateDefaultSubobject<UStatsComponent>(TEXT("StatsComponent"));
StatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &AMasterAI::CharacterCurrentStatValueUpdated);
//Setting MovementSpeed
MovementSpeedMode = EMovementSpeedMode::Jogging;
WalkingSpeed = 200.f;
JoggingSpeed = 500.f;
SprintSpeed = 700.f;
PelvisBoneName = TEXT("pelvis");
}
void AMasterAI::BeginPlay()
{
// Call the base class
Super::BeginPlay();
StatsComponent->InitializeStats();
}
float AMasterAI::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float fDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
UAttackDamageType* damageTypeClass = Cast<UAttackDamageType>(DamageEvent.DamageTypeClass->GetDefaultObject());
if(!IsValid(damageTypeClass))
return false;
if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
{
const FPointDamageEvent* PointDamageEvent = static_cast<const FPointDamageEvent*>(&DamageEvent);
//스텟 관련 처리
StatsComponent->TakeDamageOnStat(Damage);
//앞에서 맞았는지 뒤에서 맞았는지 판별
bHitFront = UKismetMathLibrary::InRange_FloatFloat(this->GetDotProductTo(EventInstigator->GetPawn()), -0.1f, 1.f);
LastHitInfo = PointDamageEvent->HitInfo;
//play sound, effect
ApplyImpactEffect(damageTypeClass->DamageType);
if (CanReceiveHitReaction())
ApplyHitReaction(damageTypeClass->DamageType);
}
return fDamage;
}
void AMasterAI::ContinueAttack_Implementation()
{
if (CombatComponent->GetIsAttackSaved())
{
CombatComponent->SetIsAttackSaved(false);
if (StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Attacking)
StateManagerComponent->SetCurrentState(FGameplayTag::EmptyTag);
AttackEvent();
}
}
void AMasterAI::ResetAttack_Implementation()
{
CombatComponent->ResetAttack();
}
void AMasterAI::ResetCombat_Implementation()
{
CombatComponent->ResetAttack();
StateManagerComponent->ResetState();
StateManagerComponent->SetCurrentAction(FGameplayTag::EmptyTag);
}
FRotator AMasterAI::GetDesiredRotation_Implementation()
{
FVector InputVector = GetCharacterMovement()->GetLastInputVector();
if (InputVector.Equals(FVector(0, 0, 0), 0.001f))
return GetActorRotation();
else
return UKismetMathLibrary::MakeRotFromX(GetLastMovementInputVector());
}
bool AMasterAI::CanReceiveDamage_Implementation()
{
bool result;
result = (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
return result;
}
void AMasterAI::SetCanMove_Implementation(bool inputCanMove)
{
bCanMove = inputCanMove;
}
bool AMasterAI::CanBeTargeted()
{
return (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
}
void AMasterAI::OnTargeted(bool bIsTargeted)
{
TargetingWidgetComponent->SetVisibility(bIsTargeted);
}
void AMasterAI::SetMovementSpeedMode(EMovementSpeedMode NewSpeedMode)
{
if (NewSpeedMode == MovementSpeedMode)
return;
MovementSpeedMode = NewSpeedMode;
switch (MovementSpeedMode)
{
case EMovementSpeedMode::Walking:
GetCharacterMovement()->MaxWalkSpeed = WalkingSpeed;
break;
case EMovementSpeedMode::Jogging:
GetCharacterMovement()->MaxWalkSpeed = JoggingSpeed;
break;
case EMovementSpeedMode::Sprinting:
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
break;
default:
break;
}
}
void AMasterAI::CharacterStateBegin(FGameplayTag CharState)
{
if (FGameplayTag::EmptyTag == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Attacking == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Dodging == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_GeneralActionState == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Dead == CharState)
{
PerformDeath();
}
else if (FCombatGameplayTags::Get().Character_State_Disable == CharState)
{/*None*/}
}
void AMasterAI::CharacterStateEnd(FGameplayTag CharState)
{
if (FGameplayTag::EmptyTag == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Attacking == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Dodging == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_GeneralActionState == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Dead == CharState)
{/*None*/}
else if (FCombatGameplayTags::Get().Character_State_Disable == CharState)
{/*None*/}
}
void AMasterAI::CharacterCurrentStatValueUpdated(EStats statType, float value)
{
if (!(statType == EStats::Health) || value > 0.f)
return;
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Dead);
}
void AMasterAI::ToggleCombatEvent()
{
if (!CanPerformToggleCombat())
return;
if (!CombatComponent->GetCombatEnabled())
PerformAction(FCombatGameplayTags::Get().Character_State_GeneralActionState, FCombatGameplayTags::Get().Character_Action_EnterCombat, 0);
else
PerformAction(FCombatGameplayTags::Get().Character_State_GeneralActionState, FCombatGameplayTags::Get().Character_Action_ExitCombat, 0);
}
void AMasterAI::AttackEvent()
{
if (!CanPerformAttack())
return;
if (CombatComponent->GetCombatEnabled())
PerformAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount());
else
ToggleCombatEvent();
}
void AMasterAI::ChargeAttackEvent()
{
if (CanPerformAttack())
PerformAttack(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack, CombatComponent->GetAttackCount());
}
void AMasterAI::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
{
if (!GetMesh())
return;
GetMesh()->SetPhysicsLinearVelocity(GetActorForwardVector() * -InitialSpeed, false, PelvisBoneName);
}
void AMasterAI::EnableRagdoll()
{
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None, 0); //movement 더 이상 없게 바꿈
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore); //충돌무시
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore); //카메라 충돌무시
FAttachmentTransformRules rules(EAttachmentRule::KeepWorld, EAttachmentRule::KeepWorld, EAttachmentRule::KeepWorld, true);
GetMesh()->SetCollisionProfileName(TEXT("ragdoll"), true); //ragdoll 로 변경
GetMesh()->SetAllBodiesBelowSimulatePhysics(PelvisBoneName, true, true);
GetMesh()->SetAllBodiesBelowPhysicsBlendWeight(PelvisBoneName, 1.f);
}
void AMasterAI::DisableSprint()
{
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
if (World)
World->GetTimerManager().ClearTimer(StaminaTimerHandle);
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
SetMovementSpeedMode(EMovementSpeedMode::Jogging);
}
void AMasterAI::SprintStaminaCost()
{
if (!CanPerformSprint())
{
DisableSprint();
return;
}
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -2.f);
if (StatsComponent->GetCurrentStatValue(EStats::Stamina) < 10.f)
{
DisableSprint();
return;
}
}
void AMasterAI::ApplyHitReaction(EDamageType InDamageType)
{
switch (InDamageType)
{
case EDamageType::None:
PerformHitStun();
break;
case EDamageType::MeleeDamage:
PerformHitStun();
break;
case EDamageType::KnockdownDamage:
PerformKnockdown();
break;
default:
break;
}
}
void AMasterAI::ApplyImpactEffect(EDamageType InDamageType)
{
//Play Sound
UGameplayStatics::PlaySoundAtLocation(this, HitSound, LastHitInfo.Location);
//Hit Effect
UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), HitEmitter, LastHitInfo.Location);
}
void AMasterAI::PerformAttack(FGameplayTag attackType, int32 attackIndex)
{
TArray<UAnimMontage*> montages = 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(FCombatGameplayTags::Get().Character_State_Attacking);
StateManagerComponent->SetCurrentAction(attackType);
PlayAnimMontage(attackMontage);
int idx = attackIdx + 1;
if (idx >= montages.Num())
idx = 0;
CombatComponent->SetAttackCount(idx);
}
}
bool AMasterAI::PerformAction(FGameplayTag characterState, FGameplayTag characterAction, int32 montageIndex)
{
TArray<UAnimMontage*> montages = 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 AMasterAI::PerformDeath()
{
EnableRagdoll();
ApplyHitReactionPhysicsVelocity(2000.f); //충돌을 좀 더 그럴싸하게 하기 위해서 뒷방향으로 충격
FTimerHandle deathTimer;
GetWorld()->GetTimerManager().SetTimer(deathTimer, FTimerDelegate::CreateLambda([&]()
{
this->Destroy();
}), 4.f, false); // 4초 후에 object 삭제
}
bool AMasterAI::PerformHitStun()
{
UAnimMontage* hitMontage = nullptr;
if(bHitFront)
hitMontage = HitStunFrontMontage;
else
hitMontage = HitStunBackMontage;
if(!IsValid(hitMontage))
return false;
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Disable);
PlayAnimMontage(hitMontage);
return true;
}
bool AMasterAI::PerformKnockdown()
{
UAnimMontage* hitMontage = nullptr;
if(bHitFront)
hitMontage = KnockdownFrontMontage;
else
hitMontage = KnockdownBackMontage;
if(!IsValid(hitMontage))
return false;
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Disable);
PlayAnimMontage(hitMontage);
return true;
}
bool AMasterAI::CanPerformToggleCombat()
{
bool ReturnValue = true;
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Attacking);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dodging);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Disable);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_GeneralActionState);
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
ReturnValue &= !GetCharacterMovement()->IsFalling();
return ReturnValue;
}
bool AMasterAI::CanPerformAttack()
{
bool ReturnValue = true;
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Attacking);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dodging);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Disable);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_GeneralActionState);
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
ReturnValue &= (StatsComponent->GetCurrentStatValue(EStats::Stamina) >= 10.f);
return ReturnValue;
}
bool AMasterAI::CanJumping()
{
bool ReturnValue = true;
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dodging);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Disable);
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_GeneralActionState);
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
ReturnValue &= !GetCharacterMovement()->IsFalling();
return ReturnValue;
}
bool AMasterAI::CanReceiveHitReaction()
{
bool ReturnValue = true;
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
return ReturnValue;
}
bool AMasterAI::CanPerformSprint()
{
return (FMath::IsNearlyEqual(GetVelocity().Length(), 0.f)) == false;
}
FGameplayTag AMasterAI::GetDesiredAttackType()
{
if (GetCharacterMovement()->IsFalling())
return FCombatGameplayTags::Get().Character_Action_Attack_FallingAttack;
if (GetCombatMovementSpeedMode() == EMovementSpeedMode::Sprinting)
return FCombatGameplayTags::Get().Character_Action_Attack_SprintAttack;
return FCombatGameplayTags::Get().Character_Action_Attack_LightAttack;
}
TArray<UAnimMontage*> AMasterAI::GetActionMontage(FGameplayTag characterAction)
{
TArray<UAnimMontage*> outputArr;
if (FCombatGameplayTags::Get().Character_Action_Attack_CloseRange == characterAction)
outputArr = CloseRangeAttackMontage;
else if (FCombatGameplayTags::Get().Character_Action_Dodge == characterAction)
outputArr = DodgeMontage;
else if (FCombatGameplayTags::Get().Character_Action_EnterCombat == characterAction)
outputArr.Add(EnterCombat);
else if (FCombatGameplayTags::Get().Character_Action_ExitCombat == characterAction)
outputArr.Add(ExitCombat);
return outputArr;
}

View File

@ -0,0 +1,147 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interface/CombatInterface.h"
#include "Components/StateManagerComponent.h"
#include "Components/StatsComponent.h"
#include "Components/TargetingComponent.h"
#include "Definitions/GameEnums.h"
#include "Interface/TargetingInterface.h"
#include "MasterAI.generated.h"
UCLASS()
class D1_API AMasterAI : public ACharacter, public ICombatInterface, public ITargetingInterface
{
GENERATED_BODY()
public:
AMasterAI();
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="UI", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UWidgetComponent> TargetingWidgetComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UCombatComponent> CombatComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<UStateManagerComponent> StateManagerComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<UStatsComponent> StatsComponent;
protected:
// APawn interface
virtual void BeginPlay() override;
virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
public:
// Inherited via ICombatInterface
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ContinueAttack();
virtual void ContinueAttack_Implementation() override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ResetAttack();
virtual void ResetAttack_Implementation() override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ResetCombat();
virtual void ResetCombat_Implementation() override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
FRotator GetDesiredRotation();
virtual FRotator GetDesiredRotation_Implementation() override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
bool CanReceiveDamage();
virtual bool CanReceiveDamage_Implementation() override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void SetCanMove(bool inputCanMove);
virtual void SetCanMove_Implementation(bool inputCanMove) override;
virtual EMovementSpeedMode GetCombatMovementSpeedMode() override { return GetMovementSpeedMode(); }
// Inherited via ITargetingInterface
virtual bool CanBeTargeted() override;
virtual void OnTargeted(bool bIsTargeted) override;
public:
void SetMovementSpeedMode(EMovementSpeedMode NewSpeedMode);
FORCEINLINE EMovementSpeedMode GetMovementSpeedMode() const { return MovementSpeedMode; }
private://Delegate
void CharacterStateBegin(FGameplayTag CharState);
void CharacterStateEnd(FGameplayTag CharState);
void CharacterCurrentStatValueUpdated(EStats statType, float value);
private:
void ToggleCombatEvent();
void AttackEvent();
void ChargeAttackEvent();
void ApplyHitReactionPhysicsVelocity(float InitialSpeed);
void EnableRagdoll();
void DisableSprint();
void SprintStaminaCost();
void ApplyHitReaction(EDamageType InDamageType);
void ApplyImpactEffect(EDamageType InDamageType);
private:
void PerformAttack(FGameplayTag attackType, int32 attackIndex);
bool PerformAction(FGameplayTag characterState, FGameplayTag characterAction, int32 montageIndex);
void PerformDeath();
bool PerformHitStun();
bool PerformKnockdown();
protected: //Check Func
bool CanPerformToggleCombat();
bool CanPerformAttack();
bool CanJumping();
bool CanReceiveHitReaction();
bool CanPerformSprint();
FGameplayTag GetDesiredAttackType();
TArray<UAnimMontage*> GetActionMontage(FGameplayTag characterAction);
public: //Getter
FORCEINLINE class UBehaviorTree* GetBeHaviorTree() {return BehaviorTree;}
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Initialization", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UBehaviorTree> BehaviorTree;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit", meta = (AllowPrivateAccess = "true"))
FName PelvisBoneName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class USoundBase> HitSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UNiagaraSystem> HitEmitter;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MovementSpeed", meta = (AllowPrivateAccess = "true"))
EMovementSpeedMode MovementSpeedMode;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MovementSpeed", meta = (AllowPrivateAccess = "true"))
float WalkingSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MovementSpeed", meta = (AllowPrivateAccess = "true"))
float JoggingSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MovementSpeed", meta = (AllowPrivateAccess = "true"))
float SprintSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAnimMontage> HitStunFrontMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAnimMontage> HitStunBackMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAnimMontage> KnockdownFrontMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAnimMontage> KnockdownBackMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Attacks", meta = (AllowPrivateAccess = "true"))
TArray<TObjectPtr<UAnimMontage>> CloseRangeAttackMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Actions", meta = (AllowPrivateAccess = "true"))
TArray<TObjectPtr<UAnimMontage>> DodgeMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Actions", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAnimMontage> EnterCombat;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Actions", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UAnimMontage> ExitCombat;
private:
FTimerHandle StaminaTimerHandle;
private:
bool bCanMove = true;
bool bHitFront;
FHitResult LastHitInfo;
};

View File

@ -6,7 +6,7 @@
#include "Components/CombatComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
#include "KismetAnimationLibrary.h"
void UCombatAnimInstance::NativeInitializeAnimation()
{
@ -36,8 +36,8 @@ void UCombatAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
FVector2D vel = { Velocity.X, Velocity.Y };
GroundSpeed = vel.Length();
Direction = CalculateDirection(CharacterMovementComponent->Velocity, Character->GetActorRotation());
Direction = UKismetAnimationLibrary::CalculateDirection(CharacterMovementComponent->Velocity, Character->GetActorRotation());
ShouldMove = (GroundSpeed > 3.f) && (CharacterMovementComponent->GetCurrentAcceleration().Length() != 0);
}

View File

@ -51,6 +51,8 @@ ACombatCharacter::ACombatCharacter()
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
GetCharacterMovement()->GravityScale = 1.75f;
GetMesh()->SetRelativeLocationAndRotation(FVector(0.f, 0.f, -100.f), FRotator(0.f, -90.f, 0.f));
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
@ -359,18 +361,7 @@ void ACombatCharacter::LightChargeAttack(const FInputActionInstance& Instance)
AttackHeldTime = Instance.GetElapsedTime();
bAttackCharged = (AttackHeldTime >= ChargeAttackTime);
if (bAttackCharged)
{
if (CanPerformAttack())
{
PerformAttack(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack, CombatComponent->GetAttackCount());
ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon();
if (IsValid(pBaseWeapon))
{
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction());
}
}
}
ChargeAttackEvent();
}
void ACombatCharacter::HeavyAttack(const FInputActionValue& Value)
@ -556,6 +547,31 @@ void ACombatCharacter::AttackEvent()
ToggleCombatEvent();
}
void ACombatCharacter::ChargeAttackEvent()
{
if (CanPerformAttack())
{
PerformAttack(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack, CombatComponent->GetAttackCount());
ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon();
if (IsValid(pBaseWeapon))
{
StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction());
}
}
}
bool ACombatCharacter::ResetChargeAttack()
{
AttackHeldTime = 0.f;
if (bAttackCharged)
{
bAttackCharged = false;
return false;
}
else
return true;
}
void ACombatCharacter::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
{
if (!GetMesh())
@ -577,18 +593,6 @@ void ACombatCharacter::EnableRagdoll()
GetMesh()->SetAllBodiesBelowPhysicsBlendWeight(PelvisBoneName, 1.f);
}
bool ACombatCharacter::ResetChargeAttack()
{
AttackHeldTime = 0.f;
if (bAttackCharged)
{
bAttackCharged = false;
return false;
}
else
return true;
}
void ACombatCharacter::DisableSprint()
{
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
@ -644,6 +648,9 @@ void ACombatCharacter::ApplyImpactEffect(EDamageType InDamageType)
void ACombatCharacter::PerformAttack(FGameplayTag attackType, int32 attackIndex)
{
if(!CombatComponent->GetCombatEnabled())
return;
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return;

View File

@ -140,9 +140,10 @@ private://Delegate
private:
void ToggleCombatEvent();
void AttackEvent();
void ChargeAttackEvent();
bool ResetChargeAttack();
void ApplyHitReactionPhysicsVelocity(float InitialSpeed);
void EnableRagdoll();
bool ResetChargeAttack();
void DisableSprint();
void SprintStaminaCost();
void ApplyHitReaction(EDamageType InDamageType);

View File

@ -10,7 +10,7 @@ public class D1 : ModuleRules
PublicDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay",
"EnhancedInput", "GameplayTags", "Niagara"
"EnhancedInput", "AnimGraphRuntime", "GameplayTags", "Niagara",
});
PublicIncludePaths.AddRange(new string[] { "D1" });

View File

@ -86,4 +86,9 @@ void FCombatGameplayTags::InitializeNativeGameplayTags()
FName("Character.Action.Attack.SprintAttack"),
FString("Action Attack SprintAttack")
);
GameplayTags.Character_Action_Attack_CloseRange = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName("Character.Action.Attack.CloseRange"),
FString("Action Attack CloseRange")
);
}

View File

@ -29,6 +29,7 @@ public:
FGameplayTag Character_Action_Attack_HeavyAttack;
FGameplayTag Character_Action_Attack_LightAttack;
FGameplayTag Character_Action_Attack_SprintAttack;
FGameplayTag Character_Action_Attack_CloseRange;
private:
static FCombatGameplayTags GameplayTags;

View File

@ -1,75 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TestDummyCharacter.h"
#include "Engine/DamageEvents.h"
#include "Actor/BaseEquippable.h"
#include "Components/WidgetComponent.h"
#include "Kismet/GameplayStatics.h"
ATestDummyCharacter::ATestDummyCharacter()
{
//Setting Widget class is Editor to Blueprint
LockOnWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("LockOnWidget"));
LockOnWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
LockOnWidgetComponent->SetDrawSize({20.f, 20.f});
LockOnWidgetComponent->SetupAttachment(RootComponent);
LockOnWidgetComponent->SetRelativeLocation(FVector(0.f, 0.f, 40.f));
LockOnWidgetComponent->SetVisibility(false);
}
void ATestDummyCharacter::BeginPlay()
{
ACharacter::BeginPlay();
StatsComponent->InitializeStats();
for (auto armor : StartingEquipment)
{
FActorSpawnParameters spawnParam;
spawnParam.Owner = this;
spawnParam.Instigator = this;
ABaseEquippable* SpawnItem = Cast<ABaseEquippable>(GetWorld()->SpawnActor(armor, &GetActorTransform(), spawnParam));
if (SpawnItem)
SpawnItem->OnEquipped();
}
}
bool ATestDummyCharacter::CanBeTargeted()
{
FGameplayTagContainer inputContainer;
inputContainer.AddTag(FCombatGameplayTags::Get().Character_State_Dead);
return !StateManagerComponent->IsCurrentStateEqualToAny(inputContainer);
}
void ATestDummyCharacter::OnTargeted(bool bIsTargeted)
{
LockOnWidgetComponent->SetVisibility(bIsTargeted);
}
//float ATestDummyCharacter::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
//{
// float fDamage = ACharacter::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
//
// if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
// {
// const FPointDamageEvent* PointDamageEvent = static_cast<const FPointDamageEvent*>(&DamageEvent);
//
// StatsComponent->TakeDamageOnStat(Damage);
//
// //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;
//}

View File

@ -1,33 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "CombatCharacter.h"
#include "Interface/TargetingInterface.h"
#include "TestDummyCharacter.generated.h"
/**
*
*/
UCLASS()
class D1_API ATestDummyCharacter : public ACombatCharacter, public ITargetingInterface
{
GENERATED_BODY()
public:
ATestDummyCharacter();
public:
virtual void BeginPlay() override;
protected:
// Inherited via ITargetingInterface
virtual bool CanBeTargeted() override;
virtual void OnTargeted(bool bIsTargeted) override;
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
TArray<TSubclassOf<class ABaseEquippable>> StartingEquipment;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UI")
TObjectPtr<class UWidgetComponent> LockOnWidgetComponent;
};

View File

@ -0,0 +1,45 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/UI_HealthBar.h"
#include "Components/ProgressBar.h"
void UUI_HealthBar::NativeConstruct()
{
Super::NativeConstruct();
if(bUsePlayerStatsComponent)
{
if(const APlayerController* playerController = GetOwningPlayer())
StatsComponent = playerController->GetComponentByClass<UStatsComponent>();
}
InitializeHealthBar(StatsComponent, StatType);
}
void UUI_HealthBar::InitializeHealthBar(UStatsComponent* InputStatsComponent, EStats InputStatType)
{
if(!IsValid(InputStatsComponent))
return;
if(StatType != EStats::None) //Initial은 한번만 탈 수 있도록
return;
StatType = InputStatType;
StatsComponent = InputStatsComponent;
InputStatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &UUI_HealthBar::HealthBarStatValueUpdated);
HealthBarStatValueUpdated(StatType, InputStatsComponent->GetCurrentStatValue(StatType)); //최초 1번 실행
}
void UUI_HealthBar::HealthBarStatValueUpdated(const EStats Stat, const float Value) const
{
if (StatType != Stat)
return;
if (!IsValid(StatsComponent))
return;
const float Percent = Value / StatsComponent->GetMaxStatValue(StatType);
HealthBar->SetPercent(Percent);
}

View File

@ -0,0 +1,41 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/StatsComponent.h"
#include "UI_HealthBar.generated.h"
/**
*
*/
UCLASS()
class D1_API UUI_HealthBar : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(BindWidget, AllowPrivateAccess="true"))
TObjectPtr<class UProgressBar> HealthBar;
UPROPERTY(visibleAnywhere, BlueprintReadWrite, Category="Components" , meta=(AllowPrivateAccess="true"))
TObjectPtr<UStatsComponent> StatsComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Initialization" , meta=(AllowPrivateAccess="true"))
EStats StatType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Initialization" , meta=(AllowPrivateAccess="true"))
bool bUsePlayerStatsComponent;
public:
void InitializeHealthBar(UStatsComponent* InputStatsComponent, EStats InputStatType);
public:
FORCEINLINE void SetStatType(EStats InputStatType) { StatType = InputStatType; }
FORCEINLINE EStats GetStatType() { return StatType; }
public: //Delegate
void HealthBarStatValueUpdated(const EStats Stat, const float Value) const;
};