Using DamageTypes in Unreal Engine 5 with C++

Real quick let's make some DamageTypes in UE5

Unreal Engine 5 Demo Room different damage types and particle effects
DamageTypes: Gas, Fire, and Water

Game Engine

Unreal Engine 5.5.3

IDE

Rider 2024.3.6

Project Name

MyProject

OS

macOS Sequoia 15.3.1

When applying and receiving damage we can use custom DamageTypes to help determine how we want our actors to react to the damage. For example, we might have three different types of bombs and some enemies might have certain weaknesses or buffs against specific damage types.

For this quick example I'll focus on Gas, Fire, and Water DamageTypes. We'll pass this DamageType through the ApplyDamage function and the actor will react to it in the OnTakeAnyDamage delegate. You can refer to my ApplyDamage post or OnTakeAnyDamage post for quick overviews on those methods.

Start by creating custom UObject class that contains custom damage types along with a custom enumerator. The enum will be used in switch statement to return the StataicClass() that we need to pass to the ApplyDamage function. We also need separate UDamageType classes for each unique type. So we'll need a Gas, Fire, and Water class that inherit from UDamageType. Below is the header file I created for the DamageTypes.

MyDamageTypes.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "GameFramework/DamageType.h"
#include "MyDamageTypes.generated.h"

UENUM(BlueprintType)
enum class EMyDamageTypes : uint8
{
	Gas		UMETA(DisplayName = "Gas"),
	Fire	UMETA(DisplayName = "Fire"),
	Water	UMETA(DisplayName = "Water")
};

UCLASS()
class MYPROJECT_API UGasDamageType : public UDamageType
{
	GENERATED_BODY()
};

UCLASS()
class MYPROJECT_API UFireDamageType : public UDamageType
{
	GENERATED_BODY()
};

UCLASS()
class MYPROJECT_API UWaterDamageType : public UDamageType
{
	GENERATED_BODY()
};

UCLASS()
class MYPROJECT_API UMyDamageTypes : public UObject
{
	GENERATED_BODY()

public:
	static TSubclassOf<UDamageType> GetDamageTypeClass(EMyDamageTypes DamageType);
};

The GetDamageTypeClass will be a simple switch statement that returns a TSubclassOf<UDamageType>. We do this to satisfy the ApplyDamage's las parameter of TSubclassOf<UDamageType> DamageTypeClass.

MyDamageTypes.cpp

#include "MyDamageTypes.h"

TSubclassOf<UDamageType> UMyDamageTypes::GetDamageTypeClass(EMyDamageTypes DamageType)
{
	switch (DamageType)
	{
		case EMyDamageTypes::Gas:
			return UGasDamageType::StaticClass();
		case EMyDamageTypes::Fire:
			return UFireDamageType::StaticClass();
		case EMyDamageTypes::Water:
			return UWaterDamageType::StaticClass();
		default:
			return UDamageType::StaticClass();
	}
}

With our custom damage types in place we can now apply and receive damage using our newly created types. When using ApplyDamage we can do something similar to the snippet below. We'll trigger ApplyDamage inside an OnHit function and use our new GetDamageTypeClass method along with our new EMyDamageTypes enum to create our TSubclassOf<UDamageType> variable. In the snippet below we're passing in our "fire" DamageType. I think in some cases we might be able to bypass the enum and GetDamageTypeClass method all together, and just use the available classes, but having an enum and custom function might help keep things consistent throughout our project. As always, adjust as you see fit.

MyProjectile.h (example

#include "MyProject/Enums/MyDamageTypes.h"
...
void AMyProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	if (IsValid(OtherActor))
	{
		AController* InstigatorController = GetInstigatorController();
		TSubclassOf<UDamageType> DamageType = UMyDamageTypes::GetDamageTypeClass(EMyDamageTypes::Fire);

		UGameplayStatics::ApplyDamage(OtherActor, 100.f, InstigatorController, this, DamageType);
		Destroy();
	}
}

Now in our receiving actor we can perform conditional logic inside our OnTakeAnyDamage function. In this example we can pretend that our enemy is a fire creature thus making it immune to fire damage. So if it's a fire bomb, there will not be any damage applied, if it's a water bomb we'll multiply the damage by two, if it's a gas bomb we'll halve the damage, and for everything else the regular damage amount be applied.

MyCreatureCharacter.cpp (example)

#include "MyProject/Enums/MyDamageTypes.h" 

AMyMovingCharacter::AMyCreatureCharacter()
{
    ...
	OnTakeAnyDamage.AddDynamic(this, &AMyMovingCharacter::MyDamageFunction);
}


void AMyCreatureCharacter::MyDamageFunction(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{
	if (DamageType != nullptr)
	{
		if (DamageType->IsA(UGasDamageType::StaticClass()))
		{
	 		UE_LOG(LogTemp, Warning, TEXT("Gas DamageType"));
			// Creature receives half damage from gas type
			Health -= (Damage * 0.5);
	 	}
	 	else if (DamageType->IsA(UFireDamageType::StaticClass()))
	 	{
	 		UE_LOG(LogTemp, Warning, TEXT("Fire DamageType"));
	 		// Do nothing to Health, Creature is immune
	 	}
	 	else if (DamageType->IsA(UWaterDamageType::StaticClass()))
	 	{
	 		UE_LOG(LogTemp, Warning, TEXT("Water DamageType"));
	 		// Creature is weak to water, 2x damage
	 		Health -= (Damage * 2);
	 	}
	 	else
	 	{
	 		UE_LOG(LogTemp, Warning, TEXT("Default DamageType"));
	 		Health -= Damage;
	 	}
	}
	
	if (Health <= 0.f)
	{
		Destroy();
	}
}

I hope this helped. I found it a little tricky to connect the custom enum to custom DamageType static classes, but I think we eventually go there with the custom GetDamageTypeClass function.

Comments (0)

Add a Comment

Sign in to comment or like.

Delete Comment

-