Unreal Engine 5 - Array of Gameplay Effects

Currently, I am working my way through an Unreal Engine 5 Course for the Gameplay Ability System on Udemy by Stephen Ulibarri.

Link to the course:

Unreal Engine 5 - Gameplay Ability System - Top Down RPG

In the course, there are certain sections where Stephen gives you a quest to try to solve a problem on your own. These quests are usually followed up with a segment where he shows you his solution. There are also other sections called "Side Quests”. These sections are optional, and you aren’t given a solution at the end. This is a good way to practice what you have been learning and allow you to come up with a solution all by yourself. This post is a solution for the first side quest.

This side quest challenges you to create an array of different Effects that can be assigned to a Blueprint. The example I will use later is for a Fire Area blueprint that has a particle effect for fire and a bounding box. Once the player collides with the bounding box, a Gameplay Effect triggers. This allows us to apply damage to the player. This same mechanism can also be used to heal the player when they pick up potions, restore mana, etc. Since we can apply multiple different types of effects, having an array of Effects rules for the Effects allows us to apply all sorts of different effects to any given object. For example, maybe we have a spell that deals impact damage but also has a chance to apply a burn effect. We can add both of these effects with this approach.

Now that I have explained what this is for, I just wanted to share my solution for splitting the effects into an array for the first side quest.

Firstly, I created this struct to hold each Effect’s Policies and GameplayEffect’s:

USTRUCT(BlueprintType)
struct FEffectType
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
	TSubclassOf<UGameplayEffect> GameplayEffect;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
	EEffectApplicationPolicy ApplicationPolicy = EEffectApplicationPolicy::DoNotApply;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
	EEffectRemovalPolicy RemovalPolicy = EEffectRemovalPolicy::DoNotRemove
};

I also added this line to my AuraEffectActor:

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Applied Effects")
TArray<FEffectType> Effects;

Then with my OnOverlap, I refactored it so that I am looping through the Effects that have been set on the AuraEffectActor in the Editor.

This allows me to simplify it so that I don’t need to have the 3 different types of Effects (e.g Instant , Duration , Infinite ) as separate variables with similar logic

void AAuraEffectActor::OnOverlap(AActor* TargetActor)
{
	for (const FEffectType& Effect : Effects)
	{
		if (Effect.ApplicationPolicy == EEffectApplicationPolicy::ApplyOnOverlap)
		{
			ApplyEffectToTarget(TargetActor, Effect);
		}
	}
}

For OnEndOverlap , like OnOverlap , I refactored it to also loop over the Effects on the AuraEffectActor and also check the application policy if the ApplyOnEndOverlap ApplicationPolicy is on the current Effect being iterated over.

Next, I check for the DurationPolicy on the GameplayEffect using GetDefaultObject and compare it against the EGameplayEffectDurationPolicy Enum to check if the Policy on the gameplay effect is set to Infinite. From there It’s mostly the same logic.

void AAuraEffectActor::OnEndOverlap(AActor* TargetActor)
{
	for (const FEffectType& Effect : Effects)
	{
		if (Effect.ApplicationPolicy == EEffectApplicationPolicy::ApplyOnEndOverlap)
		{
			ApplyEffectToTarget(TargetActor, Effect);
		}

		if (Effect.GameplayEffect.GetDefaultObject()->DurationPolicy == EGameplayEffectDurationType::Infinite)
		{
			if (Effect.RemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
			{
				UAbilitySystemComponent* TargetAbilitySystemComponent =
					UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(
						TargetActor
					);
				if (!IsValid(TargetAbilitySystemComponent)) return;
				TArray<FActiveGameplayEffectHandle> HandlesToRemove;
				for (TTuple<FActiveGameplayEffectHandle, UAbilitySystemComponent*> HandlePair : ActiveEffectHandles)
				{
					if (TargetAbilitySystemComponent == HandlePair.Value)
					{
						TargetAbilitySystemComponent->RemoveActiveGameplayEffect(HandlePair.Key, 1);
						HandlesToRemove.Add(HandlePair.Key);
					}
				}

				for (FActiveGameplayEffectHandle& Handle : HandlesToRemove)
				{
					ActiveEffectHandles.FindAndRemoveChecked(Handle);
				}
			}
		}
	}
}

Lastly, I do another comparison with the EGameplayEffectDurationType enum to check if the DurationPolicy is set to Infinite at the bottom of my ApplyEffectToTarget function.

the function now looks like this:

void AAuraEffectActor::ApplyEffectToTarget(
	AActor* TargetActor,
	const FEffectType& Effect
)
{
	UAbilitySystemComponent* TargetAbilitySystemComponent = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(
		TargetActor
	);

	if (TargetAbilitySystemComponent == nullptr) return;

	check(Effect.GameplayEffect);
	FGameplayEffectContextHandle EffectContextHandle = TargetAbilitySystemComponent->MakeEffectContext();
	EffectContextHandle.AddSourceObject(this);
	const FGameplayEffectSpecHandle EffectSpecHandle = TargetAbilitySystemComponent->MakeOutgoingSpec(
		Effect.GameplayEffect,
		1.0f,
		EffectContextHandle
	);
	const FActiveGameplayEffectHandle ActiveEffectHandle = TargetAbilitySystemComponent->ApplyGameplayEffectSpecToSelf(
		*EffectSpecHandle.Data.Get()
	);

	if (EffectSpecHandle.Data.Get()->Def.Get()->DurationPolicy == EGameplayEffectDurationType::Infinite)
	{
		if (Effect.RemovalPolicy == EEffectRemovalPolicy::RemoveOnEndOverlap)
		{
			ActiveEffectHandles.Add(ActiveEffectHandle, TargetAbilitySystemComponent);
		}
	}
}

Now when I go to the Blueprint for a given Effect i.e BP_FireArea. I can Add multiple Effects. In the example below I have added a Heal effect when I leave the fire area so I get health back when exiting the area.

That’s pretty much it!

I’ll be sure to post more solutions as I go through the course.

Next
Next

DropCube Devlog#6 | Main Menu, Leaderboard and Release Date!