While working on The Bug Squad I run into a situation where I needed to apply damage to world actors — in my case explosive barrels — using the Gameplay Ability System.

The standard way to do this in UE is to set bCanBeDamaged
to true on the actor and override the TakeDamage
function and/or related events. This, however, doesn’t work with GAS where damage is applied via gameplay effects.
A quick search in the Unreal Slackers Discord showed the common practice is to give an AbilitySystemComponent
to these actors. Apparently, this is also the approach Epic uses in Fortnite for world objects that can be destroyed such as rocks, trees, walls, etc.
Since there can easily be hundreds of these actors in the world, it’s advisable to lazily initialize their AbilitySystemComponent
when needed (i.e. when the actor takes damage). Here’s my current implementation:
UCLASS(Abstract)
class AGameplayActor : public AActor
{
GENERATED_BODY()
public:
AGameplayActor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
UPROPERTY(Transient, Replicated)
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
/** Initializes the ability system component. */
void InitializeAbilitySystem();
protected:
/** Called on authority and clients when the ability system component is initialized. */
virtual void AbilitySystemInitialized();
// AActor interface
public:
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
void OnSubobjectCreatedFromReplication(UObject* NewSubObject) override;
};
AGameplayActor::AGameplayActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bReplicates = true;
}
void AGameplayActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams SharedParams;
SharedParams.bIsPushBased = true;
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, AbilitySystemComponent, SharedParams);
}
void AGameplayActor::OnSubobjectCreatedFromReplication(UObject* NewSubObject)
{
Super::OnSubobjectCreatedFromReplication(NewSubObject);
// Initialize ASC immediately as it is created (OnRep would be called too late)
if (AbilitySystemComponent == nullptr)
{
AbilitySystemComponent = Cast<UAbilitySystemComponent>(NewSubObject);
if (AbilitySystemComponent != nullptr)
{
AbilitySystemInitialized();
}
}
}
void AGameplayActor::InitializeAbilitySystem()
{
if (!ensure(HasAuthority()) || AbilitySystemComponent != nullptr)
{
// Don't initialize twice or on clients
return;
}
AbilitySystemComponent = NewObject<UAbilitySystemComponent>(this);
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);
AbilitySystemComponent->RegisterComponent();
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, AbilitySystemComponent, this);
// Attribute set (i.e. containing the Health attribute) can be initialized here
// Startup gameplay effects can be applied here too
AbilitySystemInitialized();
}
void AGameplayActor::AbilitySystemInitialized()
{
check(AbilitySystemComponent != nullptr);
if (!HasAuthority())
{
// This is done automatically on the server when the component is registered
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
InitializeAbilitySystem
can be called on authority whenever the AbilitySystemComponent
needs to be initialized. The same function can be used to setup attribute sets or apply startup gameplay effects.
Gameplay actors and characters can share the same base attribute set which includes health and damage attributes, which allows damage to be applied via the same code path to both.
On clients, the AbilitySystemComponent
will be created automatically via replication. The AbilitySystemInitialized
callback can be used for initialization that’s shared between server and clients.
In my project I’m calling InitializeAbilitySystem
when applying area effects via my custom FGameplayAbilityTargetData
subclasses:
TArray<FActiveGameplayEffectHandle> FMyCustomGameplayAbilityTargetData::ApplyGameplayEffectSpec(FGameplayEffectSpec& Spec,
const FPredictionKey PredictionKey)
{
// Activate gameplay actors
for (const TWeakObjectPtr<AActor>& TargetActor : GetActors())
{
AGameplayActor* GameplayActor = Cast<AGameplayActor>(TargetActor);
if (GameplayActor != nullptr && GameplayActor->GetAbilitySystemComponent() == nullptr)
{
GameplayActor->InitializeAbilitySystem();
}
}
return FGameplayAbilityTargetData::ApplyGameplayEffectSpec(Spec, PredictionKey);
}