9 minute read

Damage is a common concept in games and interactive experiences. Unreal Engine provides a handy little framework for dealing and receiving damage.

This tutorial aims to give you an introduction to the Damage system in blueprint and C++, including tips and tricks that I have learned from experience.

Damage in Unreal

To borrow a phrase from Alex Forsythe: With Unreal, damage comes standard. It’s a feature that is part of the Actor class, meaning that every actor class that you’ve created so far already natively supports dealing & receiving damage!

Let’s start this tutorial with a quick introduction to the concept of instigator. After that, we’ll move the essentials of dealing and receiving damage in both blueprint and C++.

Instigator

You might already have seen the term “instigator” a few times while working in Unreal, most likely when spawning an actor. Every actor has an Instigator property, it is a reference to a Pawn and represents the pawn/character responsible for any actions the spawned actor will do.

The instigator is usually set when you spawn the actor. In blueprints, this is done like this:

A call to SpawnActor in blueprint with the Instigator parameter

In C++, you can set the instigator pawn in the FActorSpawnParameters struct:

void AYourPawn::SpawnProjectile(const TSubclassOf<AActor> ProjectileClass)
{
    // Assert that FP_MuzzleLocation is a valid component.
    check(FP_MuzzleLocation != nullptr);

    // Spawn a projectile at a specified transform, with instigator set to this pawn.
    const FTransform& SpawnTransform = FP_MuzzleLocation->GetComponentTransform();
    FActorSpawnParameters SpawnParams;
    SpawnParams.Instigator = this;
    World->SpawnActor<AActor>(ProjectileClass, SpawnTransform, SpawnParams);
}

The concept of instigators is incredibly useful for damage: It allows a damage receiver to act differently depending on who was responsible for dealing the damage. The classroom example for this is friendly fire: If you receive damage and the instigator of the damage is someone on your own team, you might decide to ignore the damage if friendly fire is disabled.

Dealing damage

To inflict damage on an actor with blueprints, simply call the ApplyDamage function:

A call to ApplyDamage in blueprint with the relevant arguments

Let’s briefly talk about some of the parameters:

  • Damaged Actor: The actor you’re dealing damage to.
  • Event Instigator: The controller that is responsible for dealing this damage. You can obtain the instigator controller using the GetInstigatorController node.
  • Damage Causer: The actor that actually dealt this damage (e.g. a projectile or grenade).
  • Damage Type Class: An object that can influence damage calculations. This deserves a tutorial on it’s own so we will just keep it empty for now.

In addition to ApplyDamage, there are also separate, specialized functions for dealing point and radial damage. Point damage is for damage at a particular point, coming from a particular direction (like being hit with a bullet). Radial damage is for damage in a wider area, possibily affecting multiple actors (like a grenade blast). I will write a separate tutorial about these damage variants in the future.

The C++ equivalent for dealing damage is AActor::TakeDamage. While the blueprint equivalent has different functions for each damage variant (i.e. point, radius, etc.), the TakeDamage function combines all of these into one with a FDamageEvent struct to differentiate between the variants.

Dealing damage in C++ is pretty simple though, here is the equivalent for the above blueprint example:

void AYourActor::DealDamageTo(class AActor* OtherActor)
{
    // Much like the blueprint example, we're not using DamageType yet.
    OtherActor->TakeDamage(4.2f, FDamageEvent(), GetInstigatorController(), this);
}

For dealing point and radial damage, you will need to supply a FPointDamageEvent or FRadialDamageEvent with the proper arguments, instead of the FDamageEvent. For applying radial damage, I recommend using the UGameplayStatics::ApplyRadialDamage helper function instead.

Responding to damage

While any actor can receive damage, by default they will do nothing. Responding to damage in an actor is as simple as overriding the AnyDamage function in blueprint. For C++ things are a bit more tricky, we’ll get to that after the blueprint stuff.

In addition to the any damage function, there are also separate functions for receiving point and radial damage. Keep in mind that when you receive point or radial damage, AnyDamage will still get called!

The following is an example of responding to damage blueprint, simply override the AnyDamage function:

The AnyDamage node

Most of the function arguments here are the same as the values you provided to the ApplyDamage function. The only exception is Damage Type, which is now an object reference rather than a class reference, more info on this in a future tutorial.

In addition to AnyDamage, there is also the OnTakeAnyDamage event dispatcher. This event is particularly useful if you have a component that takes care of dealing with damage, rather than the actor itself:

An example of using the OnTakeAnyDamage event in an actor component

Unfortunately, the C++ function ReceiveAnyDamage in AActor was not made virtual by Epic, so it cannot be overridden. The next best way is to override the TakeDamage function instead (InternalTakeRadialDamage and InternalTakePointDamage exist for radial and point damage respectively):

float AYourActor::TakeDamage(const float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    // If you need the DamageType object like in blueprint, this is how you do it:
    UDamageType const* const DamageTypeCDO = DamageEvent.DamageTypeClass ? DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>() : GetDefault<UDamageType>();

    // Make sure to call Super so blueprint & event dispatchers still fire.
    return Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
}

Fortunately, the OnTakeAnyDamage delegate can simply be bound to in C++, just like with blueprint:

void UYourActorComponent::BeginPlay()
{
    Super::BeginPlay();

    // Subscribe to the owner receiving damage event.
    GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UYourActorComponent::OnTakeRadialDamage);
}

void UYourActorComponent::OnTakeRadialDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
{
    // Your damage handling code here..
}

Conclusion

This tutorial has covered the basics of damage: It explained the role of instigators, how damage should be dealt and how it should be received, both in blueprint and C++.

Next tutorial: Point and radial damage (coming soon™)

If you have any questions, let me know in the comments below!
Jasper

Leave a comment