From 879fb83f0eea2dd43ab126e31bcdc37854a8fd69 Mon Sep 17 00:00:00 2001 From: TjgL Date: Fri, 24 Jan 2025 13:55:58 +0100 Subject: [PATCH] Implemented hitboxes and health system for ennemies --- .../Private/Player/LMHealthComponent.cpp | 62 +++++++++++ Source/LegumeMix/Private/Player/LMHitBox.cpp | 38 +++++++ Source/LegumeMix/Private/Player/LMPlayer.cpp | 14 +++ .../Public/Player/LMHealthComponent.h | 103 ++++++++++++++++++ Source/LegumeMix/Public/Player/LMHitBox.h | 46 ++++++++ 5 files changed, 263 insertions(+) create mode 100644 Source/LegumeMix/Private/Player/LMHealthComponent.cpp create mode 100644 Source/LegumeMix/Private/Player/LMHitBox.cpp create mode 100644 Source/LegumeMix/Public/Player/LMHealthComponent.h create mode 100644 Source/LegumeMix/Public/Player/LMHitBox.h diff --git a/Source/LegumeMix/Private/Player/LMHealthComponent.cpp b/Source/LegumeMix/Private/Player/LMHealthComponent.cpp new file mode 100644 index 0000000..8be1033 --- /dev/null +++ b/Source/LegumeMix/Private/Player/LMHealthComponent.cpp @@ -0,0 +1,62 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Player/LMHealthComponent.h" + +#include "Player/LMHitBox.h" + + +ULMHealthComponent::ULMHealthComponent() +{ + PrimaryComponentTick.bCanEverTick = true; +} + +void ULMHealthComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (bMaxHealthOnStart) + Health = MaxHealth; + + RegisterHitBoxes(); +} + +void ULMHealthComponent::RegisterHitBoxes() +{ + GetOwner()->GetComponents(HitBoxes); + for (const auto HitBox : HitBoxes) + { + HitBox->OnHitBoxHit.AddUniqueDynamic(this, &ULMHealthComponent::HitBoxHit); + } +} + +void ULMHealthComponent::TakeDamage_Implementation(const float Damage) +{ + if (Damage <= 0.0f) + return; + + OnHit(Damage); + OnHealthChanged.Broadcast(Health, Damage); + + if (Health <= 0.0f) + Kill(); +} + +void ULMHealthComponent::OnHit_Implementation(const float Damage) +{ + Health = FMath::Clamp(Health - Damage, 0.0f, MaxHealth); +} + + +void ULMHealthComponent::Kill() +{ + OnKill(); + OnDeath.Broadcast(); +} + +void ULMHealthComponent::HitBoxHit(ULMHitBox* HitBox, float Damage) +{ + TakeDamage(Damage); +} + + diff --git a/Source/LegumeMix/Private/Player/LMHitBox.cpp b/Source/LegumeMix/Private/Player/LMHitBox.cpp new file mode 100644 index 0000000..ec2603a --- /dev/null +++ b/Source/LegumeMix/Private/Player/LMHitBox.cpp @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Player/LMHitBox.h" + +#include "Player/LMBulletInfo.h" + + +ULMHitBox::ULMHitBox() +{ + PrimaryComponentTick.bCanEverTick = true; +} + +void ULMHitBox::OnHit(const FLMBulletInfo& BulletInfo) +{ + const float Damage = BulletInfo.Damage; + const double Distance = FVector::Distance(BulletInfo.Origin, GetComponentLocation()); + + const float TotalDamage = CalculateDamage(Damage, Distance, BulletInfo.Falloff, BulletInfo.MaxDistance); + OnHitBoxHit.Broadcast(this, TotalDamage); +} + +float ULMHitBox::CalculateDamage_Implementation(const float Damage, const float Distance, UCurveFloat* Falloff, const float MaxDistance) +{ + const float Absorption = Damage - FlatDamageAbsorption; + const float FalloffModifier = Falloff->GetFloatValue(Distance / MaxDistance); + UE_LOG(LogTemp, Display, TEXT("Falloff : %f"), FalloffModifier); + + const float FalloffDamage = Absorption * FalloffModifier; + UE_LOG(LogTemp, Display, TEXT("Damage With Fallof: %f"), FalloffDamage) + + const float FinalDamage = FalloffDamage * DamageModifier; + UE_LOG(LogTemp, Display, TEXT("Final Damages: %f"), FinalDamage) + + return FinalDamage; +} + + diff --git a/Source/LegumeMix/Private/Player/LMPlayer.cpp b/Source/LegumeMix/Private/Player/LMPlayer.cpp index df6129a..84e12a7 100644 --- a/Source/LegumeMix/Private/Player/LMPlayer.cpp +++ b/Source/LegumeMix/Private/Player/LMPlayer.cpp @@ -7,6 +7,7 @@ #include "Camera/CameraComponent.h" #include "Ammo/LMAmmo.h" #include "Player/LMBulletInfo.h" +#include "Player/LMHitBox.h" #include "Weapon/LMWeaponManager.h" ALMPlayer::ALMPlayer() @@ -84,6 +85,19 @@ void ALMPlayer::FireBullets(const FLMBulletInfo Settings) const bool HasHit = GetWorld()->LineTraceSingleByChannel(OutHit, Settings.Origin, EndLocation, ECC_Camera); DrawDebugLineTraceSingle(GetWorld(), Settings.Origin, EndLocation, EDrawDebugTrace::ForDuration, HasHit, OutHit, FColor::Green, FColor::Red, 2.f); + + if (!HasHit) + continue; + + FString Hit = FString::Printf(TEXT("Hit %s"), *OutHit.Component->GetName()); + // GEngine->AddOnScreenDebugMessage(INDEX_NONE, 2.f, FColor::Red, Hit); + UE_LOG(LogTemp, Display, TEXT("%s"), *Hit); + + if (ULMHitBox* HitBox = Cast(OutHit.Component)) + { + UE_LOG(LogTemp, Warning, TEXT("Hit Hitbox")); + HitBox->OnHit(Settings); + } } } diff --git a/Source/LegumeMix/Public/Player/LMHealthComponent.h b/Source/LegumeMix/Public/Player/LMHealthComponent.h new file mode 100644 index 0000000..cb9a577 --- /dev/null +++ b/Source/LegumeMix/Public/Player/LMHealthComponent.h @@ -0,0 +1,103 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "LMHealthComponent.generated.h" + + +class ULMHitBox; + +UCLASS(Blueprintable, ClassGroup=(Legumix), meta=(BlueprintSpawnableComponent)) +class LEGUMEMIX_API ULMHealthComponent : public UActorComponent +{ + GENERATED_BODY() + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangedSignature, float, Health, float, Damage); + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeathSignature); + +public: + ULMHealthComponent(); + virtual void BeginPlay() override; + void Kill(); + +public: + /** + * Applies an amount of damage to the component. + * @param Damage The damage to apply the Health to. + */ + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category=Legumix) + void TakeDamage(float Damage); + + /** + * Called automatically when the component hits zero health. + */ + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category=Legumix) + void OnKill(); + + /** + * Register all hitboxes associated with this component owner. + */ + UFUNCTION(BlueprintCallable, Category=Legumix, CallInEditor) + void RegisterHitBoxes(); + + /** + * Called automatically when a hitbox is hit. + * @param HitBox The hitbox that was hit. + * @param Damage The damage received by the hitbox. + */ + UFUNCTION() + void HitBoxHit(ULMHitBox* HitBox, float Damage); + + /** + * Applies the amount of damage received to the component. + * + * Called automatically when one of this component's LMHitBox is hit. + * @param Damage The damage received. + */ + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category=Legumix) + void OnHit(float Damage); + + /** + * Adds an amount of health to the character. + * @param AddedHealth The amount of health to add. + * @param NoLimits If sets, ignore the component's MaxHealth value. + */ + UFUNCTION(BlueprintCallable, Category=Legumix, meta=(Keywords = "Refill, Health")) + void AddHealth(const float AddedHealth, const bool NoLimits = false) { Health = FMath::Clamp(Health + AddedHealth, 0.0f, NoLimits ? INT_MAX : MaxHealth); } + + /** + * Completely refill the health of the component. + */ + UFUNCTION(BlueprintCallable, Category=Legumix, meta=(Keywords = "Refill, Health")) + void FillHealth() { Health = MaxHealth; } + +public: + /** Delegate called when this component receives damages. */ + UPROPERTY(BlueprintAssignable, Category=Legumix) + FOnHealthChangedSignature OnHealthChanged; + + /** Delegate called when this component is killed, aka. Health reaches 0.*/ + UPROPERTY(BlueprintAssignable, Category=Legumix) + FOnDeathSignature OnDeath; + +private: + /** + * If set, the Health will be initialized to MaxHealth. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Legumix, meta=(AllowPrivateAccess=true)) + bool bMaxHealthOnStart = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Legumix, meta=(AllowPrivateAccess=true, ClampMin=0, UIMin=0)) + float Health = 100.f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Legumix, meta=(AllowPrivateAccess=true, ClampMin=0, UIMin=0, UIMax=1000)) + float MaxHealth = 100.f; + + /** + * A list of all associated LMHitboxes. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Legumix, meta=(AllowPrivateAccess=true)) + TArray> HitBoxes; +}; diff --git a/Source/LegumeMix/Public/Player/LMHitBox.h b/Source/LegumeMix/Public/Player/LMHitBox.h new file mode 100644 index 0000000..1990ebc --- /dev/null +++ b/Source/LegumeMix/Public/Player/LMHitBox.h @@ -0,0 +1,46 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "LMBulletInfo.h" +#include "Components/CapsuleComponent.h" +#include "LMHitBox.generated.h" + + +UCLASS(Blueprintable, ClassGroup=(Legumix), meta=(BlueprintSpawnableComponent)) +class LEGUMEMIX_API ULMHitBox : public UCapsuleComponent +{ + GENERATED_BODY() + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FHitBoxHitSignature, ULMHitBox*, HitBox, float, Damage); + +public: + ULMHitBox(); + void OnHit(const FLMBulletInfo& BulletInfo); + +public: + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category=Legumix) + float CalculateDamage(float Damage, float Distance, UCurveFloat* Falloff, float MaxDistance); + +public: + /** Delegate called when the hitbox is hit. + * @param HitBox: The Hitbox that was hit. + * @param Damage: The amount of damage done to the hitbox. + */ + UPROPERTY(BlueprintAssignable, BlueprintCallable) + FHitBoxHitSignature OnHitBoxHit; + +private: + /** + * A value to multiply the damage received by. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Legumix, meta=(AllowPrivateAccess=true)) + float DamageModifier = 1.0f; + + /** + * The Amount of damage removed when hit. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Legumix, meta=(AllowPrivateAccess=true, ClampMin=0.0f, UIMin=0.0f)) + float FlatDamageAbsorption = 0.f; +};