Hi guys,
I’ve been working with the damage system and am struggling to see a clean way for implementation.
For example, i have a fire damage type, this needs to spread to other players when they touch each, have a vfx particle system that attaches to the character on fire, and do interval based damage for a specific amount of time. Also this fire can affect more than just characters.
Now i recently created a fire damagetype subclass and passed in the world and the actor affected, i then setup timers inside this subclass and controlled all the affects that should happen to the character from within this class. This worked, however, when the fire started to affect another character (after touching each other) the timers ended for the first character on fire and began on the second, so i assume because damagetypes are not state based they don’t create their own instance so cannot run or apply functions to more than one character at a time.
My real question is, whats the best way to create a damage system, should the character have a function to check the damage type and run all functionality on itself, only querying the damage type for values like damage amount and particle vfx to spawn. Or should this all be controlled by the damagetype. I’ve not found any good examples of a damage system and would really appreciate some direction on the way to approach this.
Here’s the code i used for my damagetype:
H File
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/DamageType.h"
#include "IGCBaseDamageType.generated.h"
class AIGCCharacter;
/**
* The base damage class, all other damage classes should overwrite the process damage function and implement
the relevant logic there, checking the class of the item they are applying damage too.
*/
UCLASS()
class IGC_API UIGCBaseDamageType : public UDamageType
{
GENERATED_BODY()
public:
// constructor
UIGCBaseDamageType(const FObjectInitializer& ObjectInitializer);
UFUNCTION()
void ProcessDamage(AActor* TheActor, UWorld* TheWorld);
UFUNCTION()
void ApplyFireDamage();
UFUNCTION()
void EndFireDamage();
/** Particle to play when crabs clash claws */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VFX")
UParticleSystem* FireEffectParticle;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VFX")
UParticleSystem* FireOutParticle;
private:
FTimerHandle DamageLengthTimerHandle;
FTimerHandle DamageApplyTimerHandle;
AActor* ActorToDamage;
UWorld* World;
AIGCCharacter* FireCharacter;
};
Cpp File
#include "IGCBaseDamageType.h"
#include "IGCFireDamageType.h"
#include "IGCCharacter.h"
#include "Runtime/Engine/Classes/Particles/ParticleSystemComponent.h"
UIGCBaseDamageType::UIGCBaseDamageType(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
// fire is caused by the world
bCausedByWorld = true;
//GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Purple, "Base Damage: INITIALIZED");
}
void UIGCBaseDamageType::ProcessDamage(AActor* TheActor, UWorld* TheWorld)
{
//GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Purple, "Base Damage: Damage Processed");
// setup access to the actor to damage and the world for the timer
if (TheActor)
{
ActorToDamage = TheActor;
}
if (TheWorld)
{
World = TheWorld;
}
// If the timer is already active theres no need to start it again
if (!World->GetTimerManager().IsTimerActive(DamageLengthTimerHandle))
{
// Is the fire damage applier timer active already
if (!World->GetTimerManager().IsTimerActive(DamageApplyTimerHandle))
{
// apply fire damage to the player every x seconds
World->GetTimerManager().SetTimer(DamageApplyTimerHandle, this, &UIGCBaseDamageType::ApplyFireDamage, 0.5, true);
GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Purple, "Base Damage: Damage Timer Started");
}
// Cancel the fire damage applier after x seconds
World->GetTimerManager().SetTimer(DamageLengthTimerHandle, this, &UIGCBaseDamageType::EndFireDamage, 10, true);
}
}
void UIGCBaseDamageType::ApplyFireDamage()
{
// Setup damage to continue applying for fire duration
FPointDamageEvent TheDamage;
TheDamage.DamageTypeClass = (UIGCFireDamageType::StaticClass());
TheDamage.Damage = 40;
if (ActorToDamage)
{
// TODO cast to all affected pawn types and handle their damage behaviour
FireCharacter = Cast<AIGCCharacter>(ActorToDamage);
// if its a character
if (FireCharacter)
{
// character is on fire
FireCharacter->bIsOnFire = true;
// if the character is dead end the damage
if (FireCharacter->bIsDead)
{
// character is no longer on fire because they are DEAD
FireCharacter->bIsOnFire = false;
// stop applying the damage
World->GetTimerManager().ClearTimer(DamageApplyTimerHandle);
// clear the damage length timer
World->GetTimerManager().ClearTimer(DamageLengthTimerHandle);
GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Purple, FString::Printf(TEXT("Fire Damage: FIRE Damage Ended to %s DEAD"), *FireCharacter->GetName()));
}
GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Purple, FString::Printf(TEXT("Fire Damage: APPLYING FIRE Damage to %s"), *FireCharacter->GetName()));
FireCharacter->TakeDamage(TheDamage.Damage, TheDamage, nullptr, nullptr);
}
}
}
void UIGCBaseDamageType::EndFireDamage()
{
// are we applying damage using a timer
if (World->GetTimerManager().IsTimerActive(DamageApplyTimerHandle))
{
// character is no longer on fire
FireCharacter->bIsOnFire = false;
// stop applying the damage
World->GetTimerManager().ClearTimer(DamageApplyTimerHandle);
// clear the damage length timer
World->GetTimerManager().ClearTimer(DamageLengthTimerHandle);
GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Purple, FString::Printf(TEXT("Fire Damage: FIRE Damage Ended to %s "), *FireCharacter->GetName()));
}
}