The Games DevProgramming and Game Development. Tips, Tricks and Tutorials.

Gameplay Tag Relationships

· Updated Gameplay Ability System

Updated for UE 5.8: the sample now uses GetAssetTags() (the AbilityTags property is deprecated for game code since 5.5), no longer uses static containers, and I note that the 5.8 base DoesAbilitySatisfyTagRequirements now also reports the offending tags.

When dealing with a lot of abilities, the block and cancel tags can get confusing and hard to keep managed. By using a relationship, we can apply block, cancel and activation tags from a more central location. This lets us define which ability tags block and cancel which other abilities. Example below:

This is a relationship for the Ability tag: Gameplay.Action.Player.Reload, which will block the reload ability and cancel the player’s sprint ability.

Inside your ability you would use this gameplay tag:

Let’s create a new class, which is going to be a DataAsset, and populate it with the required fields.

// (C) 2021 InterKaos Games

#pragma once

#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Engine/DataAsset.h"
#include "KaosAbilityTagRelationship.generated.h"

/** Defines the relationship between different ability tags. */
USTRUCT()
struct FKaosAbilityTagRelationshipItem
{
	GENERATED_BODY()

	/** The tag that this relationship is about. */
	UPROPERTY(EditAnywhere, Category = Ability)
	FGameplayTag AbilityTag;

	/** This ability tag will block abilities matching these tags. */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer AbilityTagsToBlock;

	/** This ability tag will cancel abilities matching these tags. */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer AbilityTagsToCancel;

	/** This ability tag will add these tags to the Activation Required Tags. */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer ActivationRequiredTags;

	/** This ability tag will add these tags to the Activation Blocked Tags. */
	UPROPERTY(EditAnywhere, Category = Tags)
	FGameplayTagContainer ActivationBlockedTags;
};

/**
 * Mapping of how ability tags block or cancel other abilities, and additional activation
 * required and blocked tags.
 */
UCLASS()
class KAOSGAME_API UKaosAbilityTagRelationship : public UDataAsset
{
	GENERATED_BODY()

public:
	/** Fills out tags to block and cancel matching the AbilityTags passed in. */
	void GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const;

	/** Adds additional required and blocking tags matching the passed in AbilityTags. */
	void GetActivationRequiredAndBlockedTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const;

private:
	/** The list of relationships between different ability gameplay tags. */
	UPROPERTY(EditAnywhere)
	TArray<FKaosAbilityTagRelationshipItem> AbilityTagRelationships;
};

Now the corresponding cpp file:

// (C) 2021 InterKaos Games

#include "KaosAbilityTagRelationship.h"

void UKaosAbilityTagRelationship::GetAbilityTagsToBlockAndCancel(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutTagsToBlock, FGameplayTagContainer* OutTagsToCancel) const
{
	for (const FKaosAbilityTagRelationshipItem& Relationship : AbilityTagRelationships)
	{
		if (AbilityTags.HasTag(Relationship.AbilityTag))
		{
			if (OutTagsToBlock)
			{
				OutTagsToBlock->AppendTags(Relationship.AbilityTagsToBlock);
			}

			if (OutTagsToCancel)
			{
				OutTagsToCancel->AppendTags(Relationship.AbilityTagsToCancel);
			}
		}
	}
}

void UKaosAbilityTagRelationship::GetActivationRequiredAndBlockedTags(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer* OutActivationRequired, FGameplayTagContainer* OutActivationBlocked) const
{
	for (const FKaosAbilityTagRelationshipItem& Relationship : AbilityTagRelationships)
	{
		if (AbilityTags.HasTag(Relationship.AbilityTag))
		{
			if (OutActivationRequired)
			{
				OutActivationRequired->AppendTags(Relationship.ActivationRequiredTags);
			}

			if (OutActivationBlocked)
			{
				OutActivationBlocked->AppendTags(Relationship.ActivationBlockedTags);
			}
		}
	}
}

Ability System Component changes

Now we need to add a couple of things to the Ability System Component so we can make use of these relationships. Add the following to your custom ASC:

public:
	/** Adds the relationship's extra activation required and blocked tags for the given ability tags. */
	void GetRelationshipActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const;

protected:
	/** Mapping of Ability Tag to block and cancel tags. */
	UPROPERTY(EditDefaultsOnly, Category = "Abilities|GameplayTags")
	TObjectPtr<const UKaosAbilityTagRelationship> AbilityTagRelationship;

and we need to override one function (it is still virtual on UAbilitySystemComponent in 5.8 with this exact signature):

virtual void ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags) override;

Now implement these functions, using the tag relationship table:

void UKaosAbilitySystemComponent::GetRelationshipActivationTagRequirements(const FGameplayTagContainer& AbilityTags, FGameplayTagContainer& OutActivationRequired, FGameplayTagContainer& OutActivationBlocked) const
{
	if (AbilityTagRelationship)
	{
		AbilityTagRelationship->GetActivationRequiredAndBlockedTags(AbilityTags, &OutActivationRequired, &OutActivationBlocked);
	}
}

void UKaosAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags)
{
	FGameplayTagContainer AbilityBlockTags = BlockTags;
	FGameplayTagContainer AbilityCancelTags = CancelTags;

	if (AbilityTagRelationship)
	{
		AbilityTagRelationship->GetAbilityTagsToBlockAndCancel(AbilityTags, &AbilityBlockTags, &AbilityCancelTags);
	}

	Super::ApplyAbilityBlockAndCancelTags(AbilityTags, RequestingAbility, bEnableBlockTags, AbilityBlockTags, bExecuteCancelTags, AbilityCancelTags);
}

Now the block and cancel tags will work. The engine calls ApplyAbilityBlockAndCancelTags from the ability with the ability’s own asset tags, so the relationship is applied both when the ability activates (block on, cancel) and when it ends (block off).

Gameplay Ability changes

Activation Required and Activation Blocked tags need a little more work in your custom Gameplay Ability. Override the following function:

virtual bool DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;

and implement it like this. This is a hard override of the default GameplayAbility function, with the relationship lookup added:

bool UKaosGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
{
	bool bBlocked = false;
	bool bMissing = false;

	const UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
	const FGameplayTag& BlockedTag = AbilitySystemGlobals.ActivateFailTagsBlockedTag;
	const FGameplayTag& MissingTag = AbilitySystemGlobals.ActivateFailTagsMissingTag;

	// Check if any of this ability's tags are currently blocked
	if (AbilitySystemComponent.AreAbilityTagsBlocked(GetAssetTags()))
	{
		bBlocked = true;
	}

	/*
	 * Relationship related code
	 */

	const UKaosAbilitySystemComponent* KaosASC = Cast<UKaosAbilitySystemComponent>(&AbilitySystemComponent);
	FGameplayTagContainer AbilityRequiredTags = ActivationRequiredTags;
	FGameplayTagContainer AbilityBlockedTags = ActivationBlockedTags;

	// This gets the additional tags from the ASC's relationship mapping for the ability's tags.
	if (KaosASC)
	{
		KaosASC->GetRelationshipActivationTagRequirements(GetAssetTags(), AbilityRequiredTags, AbilityBlockedTags);
	}

	/*
	 * End of relationship code
	 */

	// Check the required/blocked tags for this ability
	if (AbilityBlockedTags.Num() || AbilityRequiredTags.Num())
	{
		const FGameplayTagContainer& AbilitySystemComponentTags = AbilitySystemComponent.GetOwnedGameplayTags();

		if (AbilitySystemComponentTags.HasAny(AbilityBlockedTags))
		{
			bBlocked = true;
		}

		if (!AbilitySystemComponentTags.HasAll(AbilityRequiredTags))
		{
			bMissing = true;
		}
	}

	if (SourceTags != nullptr)
	{
		if (SourceBlockedTags.Num() || SourceRequiredTags.Num())
		{
			if (SourceTags->HasAny(SourceBlockedTags))
			{
				bBlocked = true;
			}

			if (!SourceTags->HasAll(SourceRequiredTags))
			{
				bMissing = true;
			}
		}
	}

	if (TargetTags != nullptr)
	{
		if (TargetBlockedTags.Num() || TargetRequiredTags.Num())
		{
			if (TargetTags->HasAny(TargetBlockedTags))
			{
				bBlocked = true;
			}

			if (!TargetTags->HasAll(TargetRequiredTags))
			{
				bMissing = true;
			}
		}
	}

	if (bBlocked)
	{
		if (OptionalRelevantTags && BlockedTag.IsValid())
		{
			OptionalRelevantTags->AddTag(BlockedTag);
		}
		return false;
	}
	if (bMissing)
	{
		if (OptionalRelevantTags && MissingTag.IsValid())
		{
			OptionalRelevantTags->AddTag(MissingTag);
		}
		return false;
	}

	return true;
}

Notes

  • I removed the static FGameplayTagContainer scratch variables the original version used. They are shared between all abilities, which is not safe if this ever runs on more than one thread, and copying a couple of containers here is cheap.
  • The 5.8 engine version of this function is written with lambdas and additionally adds the specific blocking/missing tags to OptionalRelevantTags, not just the generic failure tag. My override only adds the generic ones. If you rely on the detailed tags, copy the engine’s function (in GameplayAbility.cpp) and add the relationship tags to its ActivationBlockedTags/ActivationRequiredTags checks instead.
  • ActivateFailTagsBlockedTag and ActivateFailTagsMissingTag are now configured in the Gameplay Abilities developer settings (Project Settings) rather than on the globals class in ini.

Hopefully that’s everything, but this should let you get the Gameplay Tags for abilities under a bit more control, especially on large projects with a lot of abilities.