diff --git a/Content/CombatSystem/Blueprints/BP_CombatCharacter.uasset b/Content/CombatSystem/Blueprints/BP_CombatCharacter.uasset index 63bb7213..a53c7515 100644 Binary files a/Content/CombatSystem/Blueprints/BP_CombatCharacter.uasset and b/Content/CombatSystem/Blueprints/BP_CombatCharacter.uasset differ diff --git a/Content/CombatSystem/UI/WBP_MainHUD.uasset b/Content/CombatSystem/UI/WBP_MainHUD.uasset new file mode 100644 index 00000000..639d4129 Binary files /dev/null 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 new file mode 100644 index 00000000..0844e1eb Binary files /dev/null and b/Content/CombatSystem/UI/WBP_StatBar.uasset differ diff --git a/D1.uproject b/D1.uproject index da7abcd1..e7b4ee10 100644 --- a/D1.uproject +++ b/D1.uproject @@ -9,7 +9,8 @@ "Type": "Runtime", "LoadingPhase": "Default", "AdditionalDependencies": [ - "Engine" + "Engine", + "UMG" ] } ], diff --git a/Source/D1/Actor/BaseWeapon.cpp b/Source/D1/Actor/BaseWeapon.cpp index 467781d5..51552a62 100644 --- a/Source/D1/Actor/BaseWeapon.cpp +++ b/Source/D1/Actor/BaseWeapon.cpp @@ -20,13 +20,29 @@ ABaseWeapon::ABaseWeapon() InputCollisionObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_Pawn)); CollisionComponent->SetCollisionObjectTypes(InputCollisionObjectTypes); CollisionComponent->SetDrawDebugType(EDrawDebugTrace::None); + + ActionStatCost.Add(ECharacterAction::LightAttack, 10.f); + ActionStatCost.Add(ECharacterAction::HeavyAttack, 15.f); + ActionStatCost.Add(ECharacterAction::ChargedAttack, 20.f); + ActionStatCost.Add(ECharacterAction::FallingAttack, 10.f); + ActionStatCost.Add(ECharacterAction::SprintAttack, 10.f); + ActionStatCost.Add(ECharacterAction::Dodge, 20.f); + + ActionDamageMultiplier.Add(ECharacterAction::LightAttack, 1.f); + ActionDamageMultiplier.Add(ECharacterAction::HeavyAttack, 1.4f); + ActionDamageMultiplier.Add(ECharacterAction::ChargedAttack, 1.5f); + ActionDamageMultiplier.Add(ECharacterAction::FallingAttack, 1.2f); + ActionDamageMultiplier.Add(ECharacterAction::SprintAttack, 1.2f); } void ABaseWeapon::OnEquipped() { SetIsEquipped(true); AActor* owner = GetOwner(); + if (!owner) + return; CombatComponent = owner->GetComponentByClass(); + OwnerStateManager = owner->GetComponentByClass(); if (CombatComponent->GetCombatEnabled()) AttachActor(HandSocketName); @@ -62,6 +78,24 @@ void ABaseWeapon::SimulateWeaponPhysics() GetItemMesh()->SetSimulatePhysics(true); } +float ABaseWeapon::GetStatCostForAction() +{ + if (!IsValid(OwnerStateManager)) + return 0.f; + + return *ActionStatCost.Find(OwnerStateManager->GetCurrentAction()); +} + +float ABaseWeapon::GetDamage() +{ + if (!IsValid(OwnerStateManager)) + return Damage; + float outDamage = *ActionDamageMultiplier.Find(OwnerStateManager->GetCurrentAction()); + outDamage = FMath::Clamp(outDamage, 1.f, outDamage) * Damage; + + return outDamage; +} + TArray ABaseWeapon::GetActionMontage(ECharacterAction characterAction) { TArray outputArr; diff --git a/Source/D1/Actor/BaseWeapon.h b/Source/D1/Actor/BaseWeapon.h index 589b4978..30b279a7 100644 --- a/Source/D1/Actor/BaseWeapon.h +++ b/Source/D1/Actor/BaseWeapon.h @@ -32,6 +32,8 @@ public: public: //Normal void SimulateWeaponPhysics(); + float GetStatCostForAction(); + float GetDamage(); public: TArray GetActionMontage(ECharacterAction characterAction); @@ -45,6 +47,8 @@ protected: TObjectPtr CombatComponent; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components") TObjectPtr CollisionComponent; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components") + TObjectPtr OwnerStateManager; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Montages") TObjectPtr EnterCombat; @@ -65,4 +69,9 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat") float Damage; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat") + TMap ActionStatCost; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat") + TMap ActionDamageMultiplier; }; diff --git a/Source/D1/CombatCharacter.cpp b/Source/D1/CombatCharacter.cpp index f87b7603..ef460fcc 100644 --- a/Source/D1/CombatCharacter.cpp +++ b/Source/D1/CombatCharacter.cpp @@ -12,6 +12,7 @@ #include "Interface/Interact.h" #include "Actor/BaseWeapon.h" #include "Components/CombatComponent.h" +#include "Components/StatsComponent.h" #include "Engine/DamageEvents.h" #include "Kismet/KismetSystemLibrary.h" #include "Kismet/KismetMathLibrary.h" @@ -71,6 +72,10 @@ ACombatCharacter::ACombatCharacter() StateManagerComponent->OnActionBegin.AddUObject(this, &ACombatCharacter::CharacterActionBegin); StateManagerComponent->OnActionEnd.AddUObject(this, &ACombatCharacter::CharacterActionEnd); + // Setting StatsComponent + StatsComponent = CreateDefaultSubobject(TEXT("StatsComponent")); + StatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &ACombatCharacter::CharacterCurrentStatValueUpdated); + //Setting MovementSpeed MovementSpeedMode = EMovementSpeedMode::Jogging; WalkingSpeed = 200.f; @@ -103,6 +108,8 @@ void ACombatCharacter::BeginPlay() ABaseEquippable* SpawnItem = Cast(GetWorld()->SpawnActor(Weapon, &GetActorTransform(), spawnParam)); if (SpawnItem) SpawnItem->OnEquipped(); + + StatsComponent->InitializeStats(); } float ACombatCharacter::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) @@ -113,7 +120,7 @@ float ACombatCharacter::TakeDamage(float Damage, FDamageEvent const& DamageEvent { const FPointDamageEvent* PointDamageEvent = static_cast(&DamageEvent); - CharacterTakeDamage(fDamage); + StatsComponent->TakeDamageOnStat(Damage); //Play Sound UGameplayStatics::PlaySoundAtLocation(this, HitSound, PointDamageEvent->HitInfo.Location); @@ -199,7 +206,6 @@ void ACombatCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerIn //LightAttack EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Triggered, this, &ACombatCharacter::LightChargeAttack); EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Completed, this, &ACombatCharacter::LightAttack); - //HeavyAttack EnhancedInputComponent->BindAction(HeavyAttackAction, ETriggerEvent::Started, this, &ACombatCharacter::HeavyAttack); @@ -308,7 +314,15 @@ void ACombatCharacter::LightChargeAttack(const FInputActionInstance& Instance) if (bAttackCharged) { if (CanPerformAttack()) + { PerformAttack(ECharacterAction::ChargedAttack, CombatComponent->GetAttackCount()); + ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon(); + if (IsValid(pBaseWeapon)) + { + StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction()); + } + } + } } @@ -326,7 +340,15 @@ void ACombatCharacter::HeavyAttack(const FInputActionValue& Value) void ACombatCharacter::Dodge(const FInputActionValue& Value) { if (CanPerformDodge()) + { PerformDodge(); + ABaseWeapon* pBaseWeapon = CombatComponent->GetMainWeapon(); + if (IsValid(pBaseWeapon)) + { + StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * pBaseWeapon->GetStatCostForAction()); + } + } + } void ACombatCharacter::ToggleWalk(const FInputActionValue& Value) @@ -340,13 +362,18 @@ void ACombatCharacter::ToggleWalk(const FInputActionValue& Value) void ACombatCharacter::StartSprint(const FInputActionValue& Value) { - SetMovementSpeedMode(EMovementSpeedMode::Sprinting); + if (CanPerformSprint()) + { + SetMovementSpeedMode(EMovementSpeedMode::Sprinting); + UWorld* World = GEngine->GetWorldFromContextObjectChecked(this); + if (World) + World->GetTimerManager().SetTimer(StaminaTimerHandle, this, &ACombatCharacter::SprintStaminaCost, 0.1f, true); + } } void ACombatCharacter::StopSprint(const FInputActionValue& Value) { - if (GetMovementSpeedMode() == EMovementSpeedMode::Sprinting) - SetMovementSpeedMode(EMovementSpeedMode::Jogging); + DisableSprint(); } void ACombatCharacter::CharacterStateBegin(ECharacterState CharState) @@ -450,6 +477,14 @@ void ACombatCharacter::CharacterActionEnd(ECharacterAction CharAction) } } +void ACombatCharacter::CharacterCurrentStatValueUpdated(EStats statType, float value) +{ + if (!(statType == EStats::Health) || value > 0.f) + return; + + StateManagerComponent->SetCurrentState(ECharacterState::Dead); +} + void ACombatCharacter::ToggleCombatEvent() { ABaseWeapon* baseWeapon = CombatComponent->GetMainWeapon(); @@ -471,20 +506,15 @@ void ACombatCharacter::AttackEvent() return; if (CombatComponent->GetCombatEnabled()) + { PerformAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount()); + if (IsValid(CombatComponent->GetMainWeapon())) + StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -1.f * CombatComponent->GetMainWeapon()->GetStatCostForAction()); + } 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()) @@ -540,6 +570,32 @@ bool ACombatCharacter::ResetChargeAttack() return true; } +void ACombatCharacter::DisableSprint() +{ + UWorld* World = GEngine->GetWorldFromContextObjectChecked(this); + if (World) + World->GetTimerManager().ClearTimer(StaminaTimerHandle); + + if (GetMovementSpeedMode() == EMovementSpeedMode::Sprinting) + SetMovementSpeedMode(EMovementSpeedMode::Jogging); +} + +void ACombatCharacter::SprintStaminaCost() +{ + if (!CanPerformSprint()) + { + DisableSprint(); + return; + } + + StatsComponent->ModifyCurrentStatValue(EStats::Stamina, -2.f); + if (StatsComponent->GetCurrentStatValue(EStats::Stamina) < 10.f) + { + DisableSprint(); + return; + } +} + void ACombatCharacter::PerformAttack(ECharacterAction attackType, int32 attackIndex) { ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon(); @@ -677,6 +733,8 @@ bool ACombatCharacter::CanPerformAttack() ECharacterState::GeneralActionState }; ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr); + ReturnValue &= (StatsComponent->GetCurrentStatValue(EStats::Stamina) >= 10.f); + return ReturnValue; } @@ -691,6 +749,7 @@ bool ACombatCharacter::CanPerformDodge() }; ReturnValue &= !StateManagerComponent->IsCurrentStateEqualToAny(inputArr); ReturnValue &= !GetCharacterMovement()->IsFalling(); + ReturnValue &= (StatsComponent->GetCurrentStatValue(EStats::Stamina) >= 10.f); return ReturnValue; } @@ -718,6 +777,11 @@ bool ACombatCharacter::CanReceiveHitReaction() return ReturnValue; } +bool ACombatCharacter::CanPerformSprint() +{ + return (FMath::IsNearlyEqual(GetVelocity().Length(), 0.f)) == false; +} + ECharacterAction ACombatCharacter::GetDesiredAttackType() { if (GetCharacterMovement()->IsFalling()) diff --git a/Source/D1/CombatCharacter.h b/Source/D1/CombatCharacter.h index 96cde647..37d2eed3 100644 --- a/Source/D1/CombatCharacter.h +++ b/Source/D1/CombatCharacter.h @@ -8,6 +8,7 @@ #include "InputAction.h" #include "Interface/CombatInterface.h" #include "Components/StateManagerComponent.h" +#include "Components/StatsComponent.h" #include "Definitions/GameEnums.h" #include "CombatCharacter.generated.h" @@ -119,16 +120,18 @@ private://Delegate void CharacterStateEnd(ECharacterState CharState); void CharacterActionBegin(ECharacterAction CharAction); void CharacterActionEnd(ECharacterAction CharAction); + void CharacterCurrentStatValueUpdated(EStats statType, float value); private: void ToggleCombatEvent(); void AttackEvent(); - void CharacterTakeDamage(float InDamage); void ApplyHitReactionPhysicsVelocity(float InitialSpeed); void EnableRagdoll(); void SetMovementSpeedMode(EMovementSpeedMode NewSpeedMode); FORCEINLINE EMovementSpeedMode GetMovementSpeedMode() const { return MovementSpeedMode; } bool ResetChargeAttack(); + void DisableSprint(); + void SprintStaminaCost(); private: void PerformAttack(ECharacterAction attackType, int32 attackIndex); @@ -142,13 +145,17 @@ private: bool CanPerformDodge(); bool CanJumping(); bool CanReceiveHitReaction(); + bool CanPerformSprint(); ECharacterAction GetDesiredAttackType(); public: UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true")) TObjectPtr CombatComponent; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true")) - TObjectPtr StateManagerComponent; + TObjectPtr StateManagerComponent; + + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true")) + TObjectPtr StatsComponent; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon", meta = (AllowPrivateAccess = "true")) TSubclassOf Weapon; @@ -180,6 +187,9 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats", meta = (AllowPrivateAccess = "true")) float Health; +private: + FTimerHandle StaminaTimerHandle; + private: bool IsHeavyAttack; float AttackHeldTime; diff --git a/Source/D1/Components/StatsComponent.cpp b/Source/D1/Components/StatsComponent.cpp index 579fffbc..182ac2c7 100644 --- a/Source/D1/Components/StatsComponent.cpp +++ b/Source/D1/Components/StatsComponent.cpp @@ -13,6 +13,7 @@ UStatsComponent::UStatsComponent() PrimaryComponentTick.bCanEverTick = true; // ... + StaminaRegenRate = 2.f; } @@ -70,9 +71,9 @@ void UStatsComponent::TakeDamageOnStat(float inDamage) float armor = GetCurrentStatValue(EStats::Armor); calDamage = (inDamage / (inDamage + armor)) * inDamage; calDamage = FMath::Clamp(calDamage, 0.f, calDamage); - ModifyCurrentStatValue(EStats::Health, -calDamage); + ModifyCurrentStatValue(EStats::Health, -calDamage, false); - FString debugStr = FString::Printf(TEXT("Damage : %d"), calDamage); + FString debugStr = FString::Printf(TEXT("Damage : %f"), calDamage); GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr); if (GetCurrentStatValue(EStats::Health) <= 0.f) @@ -109,12 +110,20 @@ void UStatsComponent::StartRegen(EStats statType) void UStatsComponent::RegenerateStamina() { - //TODO : ¿©±âºÎÅÍ ½ÃÀÛ + float curStamina = StaminaRegenRate + GetCurrentStatValue(EStats::Stamina); + curStamina = FMath::Clamp(curStamina, 0.f, GetMaxStatValue(EStats::Stamina)); + SetCurrentStatValue(EStats::Stamina, curStamina); + + if (GetCurrentStatValue(EStats::Stamina) >= GetMaxStatValue(EStats::Stamina)) + { + UWorld* World = GEngine->GetWorldFromContextObjectChecked(this); + if (World) + World->GetTimerManager().ClearTimer(RegenTimerHandle); + } } float UStatsComponent::GetBaseStatValue(EStats stat) { - return BaseStats.Find(stat)->BaseValue; } diff --git a/Source/D1/Components/StatsComponent.h b/Source/D1/Components/StatsComponent.h index 3e0ab4eb..20854780 100644 --- a/Source/D1/Components/StatsComponent.h +++ b/Source/D1/Components/StatsComponent.h @@ -24,7 +24,10 @@ struct FBaseStat FBaseStat() : BaseValue(0.f), MaxValue(0.f) {} FBaseStat(float InputBaseValue, float InputMaxValue) : BaseValue(InputBaseValue), MaxValue(InputMaxValue) {} + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BaseStat, meta = (AllowPrivateAccess = "true")) float BaseValue; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BaseStat, meta = (AllowPrivateAccess = "true")) float MaxValue; }; @@ -64,14 +67,15 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BaseStats, meta = (AllowPrivateAccess = "true")) TMap BaseStats; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Stats, meta = (AllowPrivateAccess = "true")) - TMap CurrentStats; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StatRegen, meta = (AllowPrivateAccess = "true")) float StaminaRegenRate; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Stats, meta = (AllowPrivateAccess = "true")) + TMap CurrentStats; + +public: //Delegate + FOnCurrentStatValueUpdated OnCurrentStatValueUpdated; + private: FTimerHandle RegenTimerHandle; -private: - FOnCurrentStatValueUpdated OnCurrentStatValueUpdated; }; diff --git a/Source/D1/UI/UI_StatBar.cpp b/Source/D1/UI/UI_StatBar.cpp new file mode 100644 index 00000000..a3fbd52b --- /dev/null +++ b/Source/D1/UI/UI_StatBar.cpp @@ -0,0 +1,10 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "UI/UI_StatBar.h" +#include "Components/ProgressBar.h" + +void UUI_StatBar::PreConstruct() +{ + +} diff --git a/Source/D1/UI/UI_StatBar.h b/Source/D1/UI/UI_StatBar.h new file mode 100644 index 00000000..aebcebea --- /dev/null +++ b/Source/D1/UI/UI_StatBar.h @@ -0,0 +1,22 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "UI_StatBar.generated.h" + +/** + * + */ +UCLASS() +class D1_API UUI_StatBar : public UUserWidget +{ + + GENERATED_BODY() +protected: + virtual void PreConstruct() override; +public: + UPROPERTY(EditAnywhere, meta = (BindWidget)) + class UProgressBar* StatBar; +};