Gameplay Abilities·Unreal Engine·Gameplay Ability System

How to create Attribute Sets using Unreal Gameplay Ability System

Gameplay Ability System Documentation on how to create Attribute Sets to hold your character stats

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, one of those is the Attribute Set [ UAttributeSet ]

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

These 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 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 actor, 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 about 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 we have used the 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 returning 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.

Recap

A few things worth keeping straight:

  • Declare each attribute as an FGameplayAttributeData marked UPROPERTY(), then run the ATTRIBUTE_ACCESSORS macro right underneath it. The macro generates the getters and setters the rest of GAS expects (GetHealthAttribute(), GetHealth(), SetHealth(), InitHealth()).
  • If your actor already has an Ability System Component, creating the attribute set as a default subobject is enough. The ASC finds it during initialization, with no extra registration on your part.
  • Attributes hold two floats, a BaseValue and a CurrentValue. Initialize with SetNumericAttributeBase (writes the base) and read with GetNumericAttribute (returns the current value after temporary modifiers). Mixing those two up is the usual source of confusion.

What this doesn’t cover

This is single-machine setup. It does not cover replicating attributes to clients (that needs GetLifetimeReplicatedProps and an OnRep per attribute), meta attributes like an incoming-damage accumulator, or clamping values in PreAttributeChange. It also sets values directly instead of going through Gameplay Effects, which is how you actually modify attributes during gameplay. Each of those is its own post.

Keep reading

GiveAbilityAndActivateOnce: One-Shot Abilities Without the Cleanup

GiveAbilityAndActivateOnce: One-Shot Abilities Without the Cleanup

Grant an ability, activate it once, and let GAS remove the spec by itself.
From Wait Delays to Play Montage: 10 useful GAS Ability Tasks

From Wait Delays to Play Montage: 10 useful GAS Ability Tasks

Don't wait a Delay to Play your Montages, use Gameplay Ability Tasks
Learning about Ability Instancing Policy in GAS

Learning about Ability Instancing Policy in GAS

So many instancing types!