Damage in Unreal Engine 1: Introduction

9 minute read

Damage is a common concept in video games. Because of this, one of the default features in Unreal Engine is a framework for dealing and receiving damage.

This tutorial is the first in a series about damage, and aims to give you an introduction to the Damage system in blueprint and C++.

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 look into how damage should be dealt and received, both in blueprint and C++.

Instigator

If you’ve been working in Unreal, you might already have seen the term “instigator” a couple of times. 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, like dealing damage. A very practical example is a cannonball, the instigator would be the pawn firing the cannon.

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)
{
    // FP_MuzzleLocation must be 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);
}

It is also possible to change the instigator after spawning by simply calling SetInstigator.

The concept of instigators is incredibly useful for damage: It allows a damage receiver to act 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). More about point and radial damage in a future tutorial.

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

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 introduced the concept 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

Comments