How to create Attribute Sets using Unreal Gameplay Ability System

What is an Attribute Set?

When using the Unreal Gameplay Ability System we have a set of C++ classes at our disposal that makes our lives easier, on of those are the Attribute Sets [ UAttributeSet ]

The best way to understand GAS Attribute Sets is as a container of float values.

This values can represent anything ( Health, MovementSpeed, RemainingAmmo, CooldownDuration... ) whatever we want. And one of the advantages of holding these values in a Gameplay Ability System Attribute Set is that we are able to modify them in a special way using what is called an Aggregator.

How to create an Attribute Set

As there is not much Gameplay Ability System documentation it might be difficult to get started. The first thing we will need to do is to create a new class in Unreal and inherit from UAttributeSet. This will give us all the functionality we need.


UCLASS()
class UQuodHealthAttributeSet : public UAttributeSet
{
	GENERATED_BODY()
};

Once we have done that we will be able to start adding the attributes to the set. To do that we will see that we will declare them in a very specific way to allow us to get the full benefits of using them with GAS.

Looking at the AttributeSet.h file Epic recommends creating a macro to make our lives easier so we will do that.


#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

Now we can start declaring our attributes, as an example we will declare a Health attribute as a property inside our HealthAttributeSet.


UPROPERTY()
FGameplayAttributeData Health = 100.0f;
ATTRIBUTE_ACCESSORS(UQuodHealthAttributeSet, Health)


As you see we mark the attribute as a UPROPERTY, then we declare the attribute with type FGameplayAttributeData and give it a default value. Finally, we use the macro ATTRIBUTE_ACCESSORS that we have previously created with the name of our class and the name of the attribute.

Internally what this macro will do is create setters and getters for us so working with the attribute becomes easier.

Full Attribute Set code result

We can now add as many attributes as we want the same way, for instance if we wanted to add a MaxHealth attribute the resulting full code would look like this.


#pragma once
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "QuodHealthAttributeSet.generated.h"

// Helper macro to define the accessors for an attribute
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

// Used to hold information about the health of our actors
UCLASS()
class UQuodHealthAttributeSet : public UAttributeSet
{
	GENERATED_BODY()

public:

	// Current health of the owner
	UPROPERTY()
	FGameplayAttributeData Health = 100.0f;
	ATTRIBUTE_ACCESSORS(UQuodHealthAttributeSet, Health)

	// Max health that the owner can have
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FGameplayAttributeData MaxHealth= 100.0f;
	ATTRIBUTE_ACCESSORS(UQuodHealthAttributeSet, MaxHealth)
};

Giving the UAttributeSet to our actors

There are multiple ways to give an Attribute Set to an actors, but if our actor already has a UAbilitySytemComponent the easiest way is to simply declare the attribute set as a SubObject of that actor.


// .h file
// ...
// Declare a pointer to hold the attribute set
UPROPERTY()
UQuodHealthAttributeSet* HealthAttributeSet = nullptr;
// ...

// .cpp file, in the constructor
// ...
// Create the actual attribute set subobject
HealthAttributeSet = CreateDefaultSubobject(TEXT("HealthAttributeSet"));
// ...

By doing this the GAS Ability System Component will automatically find the attribute set during initialization so simply by having a subobject of that type everything will work automatically.

That being said, if we want to add attributes in a dynamic way, the Ability System Component also supports other ways of adding the attributes:


// Can be used to get an attribute and create it if does not exists
const UAttributeSet*	GetOrCreateAttributeSubobject(TSubclassOf AttributeClass);

// Called as a template so like AbilitySystemComponent->AddSet();
// Will create the attribute by internally using GetOrCreateAttributeSubobject
template 
const T*  AddSet();

// Adds the attribute set from an existing subobject
template 
const T* AddAttributeSetSubobject(T* Subobject);

How to modify the values of the Attributes

The ideal way to modify attributes using the Unreal Gameplay Ability System is to modify them using Gameplay Effects. That being said, this may be out of scope for this article and we will take a look in the future in another article abut GAS Gameplay Effects.

So, how do we modify attributes then without using Gameplay Effects?
We can use some of the accessors we created combined with the API of the Gameplay Ability System Component.

To modify Attribute values we will use the SetNumericAttributeBase function from the ASC.

So if we want to directly set a value we can do it like this:


// Get the ability system from your character or your actor that you want to modify attributes from.
UAbilitySystemComponent* AbilitySystemComponent = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(ActorWithTheComponent);
// Set the Health Attribute to the desired value
AbilitySystemComponent->SetNumericAttributeBase(UQuodHealthAttributeSet::GetHealthAttribute(), 500.0f);
// Set the Max Health Attribute to another value
AbilitySystemComponent->SetNumericAttributeBase(UQuodHealthAttributeSet::GetMaxHealthAttribute(), 100.0f);

As we will see in the future this is not the desired way to modify attributes during normal gameplay, but this can be useful to initialize attribute value.

How to read Gameplay Ability Attribute values

To read the current value an attribute holds we are also gonna do it through the Ability System Component, as you will see it is quite similar.

You can read values of attributes by using the GetNumericAttribute function.


// Get the ability system from your character or your actor that you want to read the attributes from.
UAbilitySystemComponent* AbilitySystemComponent = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(ActorWithTheComponent);
// Get the Health Attribute value
float CurrentHealth = AbilitySystemComponent->GetNumericAttribute(UQuodHealthAttributeSet::GetHealthAttribute());
// Get the Max Health Attribute value
float CurrentMaxHealth = AbilitySystemComponent->GetNumericAttribute(UQuodHealthAttributeSet::GetMaxHealthAttribute());

By using this code we can read the value of any attribute any time you want.

Current Value vs Base Value

You will have seen that in we have used SetNumericAttributeBase call to set the values but GetNumericAttribute to read them. If you look at the Ability System API you will find that a GetNumericAttributeBase exists, so why have we not used that one?

The way that attribute properties hold values is with the use of two float values: BaseValue and CurrentValue.

In GAS Gameplay Effects can be used to modify values but there are different kind of modifications that you can do:

Permanent Value Changes

You can modify an attribute permanently: You took damage, your movement speed has been initialized, you have spent ammo from your weapon..

Temporal Modifiers

You can modify an attribute temporarily: You get a speed boost, extra damage for 5s, a debuff that halves your health while inside a trigger...

How it works

In order to be able to support having temporary values and then returing to the "actual" value, that is why Base Value and Current Value are used.

When something modifies your value permanently what we modify is the BaseValue.

When something applies a temporal modification we modify the CurrentValue.

So that is why in the first case, when we initialized our attributes we used SetNumericAttributeBase, we want to modify the value permanently.

But when we want to read the value we want the current value, with all temporary modifications applied, which is the actual current value of the attribute. So we use GetNumericAttribute without the "Base" part.

Attribute Debugging Tools

This can get confusing, so if you want to get a hang of it since Unreal 5.1 GAS supports the use of the console command AbilitySystem.DebugAttribute

As epic states in its usage info the way to use it is as follows:

Usage: AbilitySystem.DebugAttribute [AttributeName] [AttributeName]...

So in our case we would use AbilitySystem.DebugAttribute Health MaxHealth.

This will display not only the CurrentValue but also the BaseValue, that way we can get an intuition of when one or the other changes.

Conclusion

This has been only an introduction to getting started with using Attribute Sets inside the Unreal Ability System Plugin.

Attributes are an incredibly powerful way to hold data with temporary modifications and can be used for a variety of use cases.

In this example we only created one AttributeSet, but we can have different UAttributeSet to represent Ammo for our weapons, character-specific data or whatever else we need.

In the future we will explore how we can use Gameplay Effects to modify attributes and how that allows us to start creating interesting behaviors.

I hope this articles will hope as another entry point that people can use as a sorts of Gameplay Ability System Documentation.

As always if you have any doubt don't hesitate to contact me, see you in the future.