diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index efdf9185..2840bcbf 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -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") \ No newline at end of file ++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") \ No newline at end of file diff --git a/Content/CombatSystem/Blueprints/AI/BB_Base.uasset b/Content/CombatSystem/Blueprints/AI/BB_Base.uasset new file mode 100644 index 00000000..d8ab959a Binary files /dev/null and b/Content/CombatSystem/Blueprints/AI/BB_Base.uasset differ diff --git a/Content/CombatSystem/Blueprints/AI/BP_ExampleEnemy.uasset b/Content/CombatSystem/Blueprints/AI/BP_ExampleEnemy.uasset new file mode 100644 index 00000000..7076610d Binary files /dev/null and b/Content/CombatSystem/Blueprints/AI/BP_ExampleEnemy.uasset differ diff --git a/Content/CombatSystem/Blueprints/AI/BP_HumanoidEnemy.uasset b/Content/CombatSystem/Blueprints/AI/BP_HumanoidEnemy.uasset new file mode 100644 index 00000000..fd20098a Binary files /dev/null and b/Content/CombatSystem/Blueprints/AI/BP_HumanoidEnemy.uasset differ diff --git a/Content/CombatSystem/Blueprints/BP_TestDummyCharacter.uasset b/Content/CombatSystem/Blueprints/BP_TestDummyCharacter.uasset deleted file mode 100644 index d6e6c0fe..00000000 Binary files a/Content/CombatSystem/Blueprints/BP_TestDummyCharacter.uasset and /dev/null differ diff --git a/Content/CombatSystem/UI/WBP_HealthBar.uasset b/Content/CombatSystem/UI/WBP_HealthBar.uasset new file mode 100644 index 00000000..4b9af4e3 Binary files /dev/null and b/Content/CombatSystem/UI/WBP_HealthBar.uasset differ diff --git a/Content/CombatSystem/UI/WBP_MainHUD.uasset b/Content/CombatSystem/UI/WBP_MainHUD.uasset index 9f4808c0..2d63cbde 100644 Binary files a/Content/CombatSystem/UI/WBP_MainHUD.uasset and b/Content/CombatSystem/UI/WBP_MainHUD.uasset differ diff --git a/Content/CombatSystem/UI/WBP_StatBar.uasset b/Content/CombatSystem/UI/WBP_StatBar.uasset index cab096eb..9331acf2 100644 Binary files a/Content/CombatSystem/UI/WBP_StatBar.uasset and b/Content/CombatSystem/UI/WBP_StatBar.uasset differ diff --git a/Content/Maps/ThirdPersonMap.umap b/Content/Maps/ThirdPersonMap.umap index 2bfeab4e..d6cb8a50 100644 Binary files a/Content/Maps/ThirdPersonMap.umap and b/Content/Maps/ThirdPersonMap.umap differ diff --git a/Content/__ExternalActors__/Maps/ThirdPersonMap/5/ES/FPX4CMFFPDV2LSL5SRPJ3D.uasset b/Content/__ExternalActors__/Maps/ThirdPersonMap/5/ES/FPX4CMFFPDV2LSL5SRPJ3D.uasset deleted file mode 100644 index 8c823833..00000000 Binary files a/Content/__ExternalActors__/Maps/ThirdPersonMap/5/ES/FPX4CMFFPDV2LSL5SRPJ3D.uasset and /dev/null differ diff --git a/Content/__ExternalActors__/Maps/ThirdPersonMap/E/SI/2GNUG7N8YL1CV9XGXC4B5N.uasset b/Content/__ExternalActors__/Maps/ThirdPersonMap/E/SI/2GNUG7N8YL1CV9XGXC4B5N.uasset new file mode 100644 index 00000000..21265e92 Binary files /dev/null and b/Content/__ExternalActors__/Maps/ThirdPersonMap/E/SI/2GNUG7N8YL1CV9XGXC4B5N.uasset differ diff --git a/D1.uproject b/D1.uproject index 9cf08a34..79fc194c 100644 --- a/D1.uproject +++ b/D1.uproject @@ -10,7 +10,8 @@ "LoadingPhase": "Default", "AdditionalDependencies": [ "Engine", - "UMG" + "UMG", + "AIModule" ] } ], diff --git a/Source/D1/AI/CombatAIController.cpp b/Source/D1/AI/CombatAIController.cpp new file mode 100644 index 00000000..1e2ba5af --- /dev/null +++ b/Source/D1/AI/CombatAIController.cpp @@ -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(InPawn); + if(AIpawn) + { + MasterAI = AIpawn; + RunBehaviorTree(MasterAI->GetBeHaviorTree()); + } +} diff --git a/Source/D1/AI/CombatAIController.h b/Source/D1/AI/CombatAIController.h new file mode 100644 index 00000000..f8b04472 --- /dev/null +++ b/Source/D1/AI/CombatAIController.h @@ -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 MasterAI; +}; diff --git a/Source/D1/AI/HumanoidEnemy.cpp b/Source/D1/AI/HumanoidEnemy.cpp new file mode 100644 index 00000000..3f2a9895 --- /dev/null +++ b/Source/D1/AI/HumanoidEnemy.cpp @@ -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(TEXT("HealthBar")); + static ConstructorHelpers::FClassFinder 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(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); +} diff --git a/Source/D1/AI/HumanoidEnemy.h b/Source/D1/AI/HumanoidEnemy.h new file mode 100644 index 00000000..193313a8 --- /dev/null +++ b/Source/D1/AI/HumanoidEnemy.h @@ -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 HealthBarComponent; + +protected: + virtual void BeginPlay() override; + +protected: + // Inherited via ITargetingInterface + virtual void OnTargeted(bool bIsTargeted) override; +}; diff --git a/Source/D1/AI/MasterAI.cpp b/Source/D1/AI/MasterAI.cpp new file mode 100644 index 00000000..d9376115 --- /dev/null +++ b/Source/D1/AI/MasterAI.cpp @@ -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(TEXT("TargetingWidget")); + static ConstructorHelpers::FClassFinder 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(TEXT("CombatComponent")); + + // Setting StateManagerComponent + StateManagerComponent = CreateDefaultSubobject(TEXT("StateManagerComponent")); + StateManagerComponent->OnStateBegin.AddUObject(this, &AMasterAI::CharacterStateBegin); + StateManagerComponent->OnStateEnd.AddUObject(this, &AMasterAI::CharacterStateEnd); + + // Setting StatsComponent + StatsComponent = CreateDefaultSubobject(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(DamageEvent.DamageTypeClass->GetDefaultObject()); + if(!IsValid(damageTypeClass)) + return false; + + if (DamageEvent.IsOfType(FPointDamageEvent::ClassID)) + { + const FPointDamageEvent* PointDamageEvent = static_cast(&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 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 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 AMasterAI::GetActionMontage(FGameplayTag characterAction) +{ + TArray 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; +} diff --git a/Source/D1/AI/MasterAI.h b/Source/D1/AI/MasterAI.h new file mode 100644 index 00000000..49fe241d --- /dev/null +++ b/Source/D1/AI/MasterAI.h @@ -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 TargetingWidgetComponent; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true")) + TObjectPtr CombatComponent; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true")) + TObjectPtr StateManagerComponent; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true")) + TObjectPtr 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 GetActionMontage(FGameplayTag characterAction); + +public: //Getter + FORCEINLINE class UBehaviorTree* GetBeHaviorTree() {return BehaviorTree;} + +private: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Initialization", meta=(AllowPrivateAccess="true")) + TObjectPtr BehaviorTree; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit", meta = (AllowPrivateAccess = "true")) + FName PelvisBoneName; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit", meta = (AllowPrivateAccess = "true")) + TObjectPtr HitSound; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit", meta = (AllowPrivateAccess = "true")) + TObjectPtr 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 HitStunFrontMontage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true")) + TObjectPtr HitStunBackMontage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true")) + TObjectPtr KnockdownFrontMontage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|HitReactions", meta = (AllowPrivateAccess = "true")) + TObjectPtr KnockdownBackMontage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Attacks", meta = (AllowPrivateAccess = "true")) + TArray> CloseRangeAttackMontage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Actions", meta = (AllowPrivateAccess = "true")) + TArray> DodgeMontage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Actions", meta = (AllowPrivateAccess = "true")) + TObjectPtr EnterCombat; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Montage|Actions", meta = (AllowPrivateAccess = "true")) + TObjectPtr ExitCombat; + +private: + FTimerHandle StaminaTimerHandle; + +private: + bool bCanMove = true; + bool bHitFront; + FHitResult LastHitInfo; +}; diff --git a/Source/D1/Animation/CombatAnimInstance.cpp b/Source/D1/Animation/CombatAnimInstance.cpp index 96428e6d..eff29d5f 100644 --- a/Source/D1/Animation/CombatAnimInstance.cpp +++ b/Source/D1/Animation/CombatAnimInstance.cpp @@ -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); } diff --git a/Source/D1/CombatCharacter.cpp b/Source/D1/CombatCharacter.cpp index 27183297..ce709372 100644 --- a/Source/D1/CombatCharacter.cpp +++ b/Source/D1/CombatCharacter.cpp @@ -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(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; diff --git a/Source/D1/CombatCharacter.h b/Source/D1/CombatCharacter.h index f0948181..072d969c 100644 --- a/Source/D1/CombatCharacter.h +++ b/Source/D1/CombatCharacter.h @@ -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); diff --git a/Source/D1/D1.Build.cs b/Source/D1/D1.Build.cs index abdaf358..a05893db 100644 --- a/Source/D1/D1.Build.cs +++ b/Source/D1/D1.Build.cs @@ -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" }); diff --git a/Source/D1/Definitions/CombatGameplayTags.cpp b/Source/D1/Definitions/CombatGameplayTags.cpp index a1d2ee86..2c9938c5 100644 --- a/Source/D1/Definitions/CombatGameplayTags.cpp +++ b/Source/D1/Definitions/CombatGameplayTags.cpp @@ -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") + ); } diff --git a/Source/D1/Definitions/CombatGameplayTags.h b/Source/D1/Definitions/CombatGameplayTags.h index 7ce61574..a2fe3941 100644 --- a/Source/D1/Definitions/CombatGameplayTags.h +++ b/Source/D1/Definitions/CombatGameplayTags.h @@ -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; diff --git a/Source/D1/TestDummyCharacter.cpp b/Source/D1/TestDummyCharacter.cpp deleted file mode 100644 index 19021eaf..00000000 --- a/Source/D1/TestDummyCharacter.cpp +++ /dev/null @@ -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(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(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(&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; -//} diff --git a/Source/D1/TestDummyCharacter.h b/Source/D1/TestDummyCharacter.h deleted file mode 100644 index 4fd1f5fb..00000000 --- a/Source/D1/TestDummyCharacter.h +++ /dev/null @@ -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> StartingEquipment; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UI") - TObjectPtr LockOnWidgetComponent; - -}; diff --git a/Source/D1/UI/UI_HealthBar.cpp b/Source/D1/UI/UI_HealthBar.cpp new file mode 100644 index 00000000..eec2c076 --- /dev/null +++ b/Source/D1/UI/UI_HealthBar.cpp @@ -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(); + } + + 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); +} diff --git a/Source/D1/UI/UI_HealthBar.h b/Source/D1/UI/UI_HealthBar.h new file mode 100644 index 00000000..955c75d3 --- /dev/null +++ b/Source/D1/UI/UI_HealthBar.h @@ -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 HealthBar; + + UPROPERTY(visibleAnywhere, BlueprintReadWrite, Category="Components" , meta=(AllowPrivateAccess="true")) + TObjectPtr 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; +};