Hello again everyone! Welcome to another entry in this on-going blog/development thread for my GAS Course Project. For more context of this project, please see the very first post in this thread, and as usual, you can find my socials here:
Twitch : Twitch
Discord : UE5 - GAS Course Project - JevinsCherries
Tiktok : Devin Sherry (@jevinscherriesgamedev) | TikTok
Youtube : Youtube
Twitter : https://twitter.com/jevincherries
Instagram : Jevins Cherries Gamedev (@jevinscherriesgamedev) • Instagram photos and videos
Today we are going to talk about Attributes/Attribute Sets, how to initialize attributes to default values, and how attributes are modified through Gameplay Effects. There are two methods for attribute initialization, and we will cover both in this blog post along with much more. As always, I highly recommend reading through Tranek’s documentation found here: GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project..
What are Attributes and Attribute Sets?
Classes to research on your own:
UAttributeSet: https://docs.unrealengine.com/5.3/en-US/API/Plugins/GameplayAbilities/UAttributeSet/
FGameplayAttributeData: https://docs.unrealengine.com/5.3/en-US/API/Plugins/GameplayAbilities/FGameplayAttributeData/
UGameplayEffectExecutionCalculation: https://docs.unrealengine.com/4.27/en-US/API/Plugins/GameplayAbilities/UGameplayEffectExecutionCalculat-/
Additional Reading:
https://docs.unrealengine.com/5.0/en-US/gameplay-attributes-and-attribute-sets-for-the-gameplay-ability-system-in-unreal-engine/
https://docs.unrealengine.com/5.3/en-US/gameplay-effects-for-the-gameplay-ability-system-in-unreal-engine/
Attributes are scalable float values defined by the FGameplayAttributeData
struct that allows developers to modify these values through mechanisms such as Gameplay Effects and Gameplay Effect Execution Calculation classes. What these attributes represent is up to the developer and the design of their game, however common attributes are values of character health/max health, mana/max mana, stamina/max stamina, etc. Essentially any gameplay oriented aspect of an actor that can be represented by a numeric value. Attributes are defined by Attribute Sets, which we will talk a bit about later.
BaseValue vs CurrentValue
It is recommended that attributes only be modified by gameplay effects so that the ability system component can predict the changes. More information on prediction can be found in Tranek’s documentation as well: GitHub - tranek/GASDocumentation: My understanding of Unreal Engine 5's GameplayAbilitySystem plugin with a simple multiplayer sample project.. Gameplay Effects have different duration policies that will dictate whether an attributes’ base value or its current value will be modified by the effect. The difference between these two values is:
Base Value: A permanent change to the attribute value, such as when leveling a character and permanently changing attributes such as Max Health applied via a Gameplay Effect using an Instant duration policy
Current Value: Current Value is the representation of the Base Value that is modified by temporary Gameplay Effects; effects with infinite or duration based duration policies.
Attributes can be given minimum and maximum values to clamp to, when defined using data tables and the struct FAttributeMetaData
; however it is recommended that for maximum values that can be changed through gameplay, such as leveling, these values should have their own attribute representation. In my project, I use attributes for maximum values like Max Health.
AdjustAttributeForMaxChange
An interesting concept that I found in my research is the adjustment of attributes based on changes done to their Max value attribute in order to preserve percentages between changes. As an example, let’s say that the NPC dummy character has a default Max Health value of 100.0f and a Current Health value of 50.0f; meaning they have 50% of health. What were to happen to their Current Health attribute when the Max Health attribute of the NPC were to increase by 100.0f? Without the AdjustAttributeForMaxChange, their Current Health would remain at 50.0f while their Max Health increases from 100.0f to 200.0f, causing the percentage to go from 50% to 25%. With AdjustAttributeForMaxChange, we can preserve that 50% by having the Current Health value increase to 100.0f when the Max Health is increased to 200.0f. Here is the code for this function:
// Helper function to proportionally adjust the value of an attribute when it's associated max attribute changes.
// (i.e. When MaxHealth increases, Health increases by an amount that maintains the same percentage as before)
void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute,
float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);
void UGASCourseAttributeSet::AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute,
const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty)
{
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && AbilityComp)
{
// Change current value to maintain the current Val / Max percent
const float CurrentValue = AffectedAttribute.GetCurrentValue();
float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;
AbilityComp->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
}
}
For my GAS Course Project, this function is called during PreAttributeChange():
void UGASCourseHealthAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if(Attribute == GetMaxHealthAttribute())
{
AdjustAttributeForMaxChange(CurrentHealth, MaxHealth, NewValue, GetCurrentHealthAttribute());
}
if(Attribute == GetCurrentHealthAttribute())
{
NewValue = FMath::Clamp<float>(NewValue, 0.0f, MaxHealth.GetCurrentValue());
}
}
Here is a before & after example:
Meta Attributes
Meta Attributes are attributes for temporary values intended to interact with actual attributes. The most common example I have seen, and that I am using this for, is damage. Instead of Gameplay Effects directly modifying Health attributes, we can use a temporary damage attribute value that can be modified via other attributes that act as buffs/debuffs. Here is an example use-case:
- The player character applies 10 fire damage onto a target.
- The target has an attribute called IncomingFireDamageModifer set to 1.5x
- The player character has an attribute called AllDamageModifier set to 2.0x
- When applying the 10 fire damage, we can create a custom formula that takes into consideration the above attributes to augment the damage value before actually reducing the Current Health Attribute.
- Damage = BaseDamage * (IncomingFireDamageModifer + AllDamageModifier) OR Damage = 10.0f * (1.5f + 2.0f) OR Damage = 10.0f * 3.5f || Damage = 35.0f.
I have not yet introduced damage modifier attributes to my project, but it is in my TODO list; however my execution class does have the logic needed to make the necessary modifications when the time comes. Here is what my GASCourseDamageExecution class looks like:
GASCourseDamageExecution.h
UCLASS()
class UGASCourseDamageExecution : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UGASCourseDamageExecution();
/** \brief Executes the custom gameplay effect implementation.
*
* This method is invoked when a gameplay effect with a custom execution is applied. It calculates and applies the damage to the target actor,
* taking into account any damage modifiers and tags. It also broadcasts the damage dealt event to the target and source ability system components.
*
* \param ExecutionParams The parameters for the execution of the gameplay effect.
* \param OutExecutionOutput The output data of the execution of the gameplay effect.
*/
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
GASCourseDamageExecution.cpp
struct GASCourseDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(IncomingDamage);
GASCourseDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UGASCourseHealthAttributeSet, IncomingDamage, Source, true);
}
};
static const GASCourseDamageStatics& DamageStatics()
{
static GASCourseDamageStatics DStatics;
return DStatics;
}
UGASCourseDamageExecution::UGASCourseDamageExecution()
{
RelevantAttributesToCapture.Add(DamageStatics().IncomingDamageDef);
}
void UGASCourseDamageExecution::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
UGASCourseAbilitySystemComponent* TargetAbilitySystemComponent = Cast<UGASCourseAbilitySystemComponent>(ExecutionParams.GetTargetAbilitySystemComponent());
UGASCourseAbilitySystemComponent* SourceAbilitySystemComponent = Cast<UGASCourseAbilitySystemComponent>(ExecutionParams.GetSourceAbilitySystemComponent());
AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->GetAvatarActor() : nullptr;
AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->GetAvatarActor() : nullptr;
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
// Gather the tags from the source and target as that can affect which buffs should be used
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
float Damage = 0.0f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().IncomingDamageDef, EvaluationParameters, Damage);
// Add SetByCaller damage if it exists
Damage += FMath::Max<float>(Spec.GetSetByCallerMagnitude(Data_IncomingDamage, false, -1.0f), 0.0f);
float UnmitigatedDamage = Damage; // Can multiply any damage boosters here
float MitigatedDamage = UnmitigatedDamage;
if (MitigatedDamage > 0.f)
{
// Set the Target's damage meta attribute
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().IncomingDamageProperty, EGameplayModOp::Additive, MitigatedDamage));
}
// Broadcast damages to Target ASC & SourceASC
if (TargetAbilitySystemComponent && SourceAbilitySystemComponent)
{
FGameplayEventData DamageDealtPayload;
DamageDealtPayload.Instigator = SourceAbilitySystemComponent->GetAvatarActor();
DamageDealtPayload.Target = TargetAbilitySystemComponent->GetAvatarActor();
DamageDealtPayload.EventMagnitude = MitigatedDamage;
DamageDealtPayload.ContextHandle = Spec.GetContext();
DamageDealtPayload.InstigatorTags = Spec.DynamicGrantedTags;
if(Spec.GetContext().GetHitResult())
{
FHitResult HitResultFromContext = *Spec.GetContext().GetHitResult();
DamageDealtPayload.TargetData = UAbilitySystemBlueprintLibrary::AbilityTargetDataFromHitResult(HitResultFromContext);
}
if(TargetAbilitySystemComponent->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag(FName("Status.Death"))))
{
return;
}
SourceAbilitySystemComponent->HandleGameplayEvent(FGameplayTag::RequestGameplayTag(FName("Event.Gameplay.OnDamageDealt")), &DamageDealtPayload);
TargetAbilitySystemComponent->HandleGameplayEvent(FGameplayTag::RequestGameplayTag(FName("Event.Gameplay.OnDamageReceived")), &DamageDealtPayload);
//TODO: Instead of sending event, pass in status effect tag into gameplay status table
TargetAbilitySystemComponent->ApplyGameplayStatusEffect(TargetAbilitySystemComponent, SourceAbilitySystemComponent, Spec.DynamicGrantedTags);
}
}
Responding to Attribute Changes
A very important aspect of attributes is being able to listen to their changes and have other systems, such as UI, respond to those changes. The Gameplay Ability System, by default, comes with the Ability Async task Wait for Attribute Changed
(UAbilityTask_WaitAttributeChange) that allows you to wait for changes to the specified attribute. When the event is received, you are returned the Attribute being modified, the New Value of the attribute, and the Old Value. In my GAS Course Project, I use this task to monitor the attribute changes for both CurrentHealth and MaxHealth of the NPC Dummy character in order to update the health bar UI when receiving damage, healing, or changes to the MaxHealth attribute.
In the following example, the dummy NPC character has a passive healing effect applied.

PreAttributeChange
As the name suggests, this function is called before the attribute is changed. This is for only changes made to the Current Value of the attribute, via Duration based Gameplay Effects. For my project, I use this to call AdjustAttributeForMaxChange
in order to adjust the Current Health to match the change. I will need to evaluate whether this adjustment should be made before or after the attribute change, but for now this is where I am handling it.
/**
* Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
* There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
* This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
*
* NewValue is a mutable reference so you are able to clamp the newly applied value as well.
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }
void UGASCourseHealthAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if(Attribute == GetMaxHealthAttribute())
{
AdjustAttributeForMaxChange(CurrentHealth, MaxHealth, NewValue, GetCurrentHealthAttribute());
}
if(Attribute == GetCurrentHealthAttribute())
{
NewValue = FMath::Clamp<float>(NewValue, 0.0f, MaxHealth.GetCurrentValue());
}
}
PostAttributeChange
This function gets called after the current attribute value has been changed. I am not sure what sort of logic should be placed here, so any information anyone has on this would be super useful.
/** Called just after any modification happens to an attribute. */
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) { }
void UGASCourseHealthAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue,
float NewValue)
{
Super::PostAttributeChange(Attribute, OldValue, NewValue);
}
PreAttributeBaseChange
This is the equivalent to PreAttributeChange but for the Base Value of an Attribute which is modified by Instant Gameplay Effects. I am still figuring out whether or not the AdjustAttributeForMaxChange should be in both PreAttributeChange & PreAttributeBaseChange or in the PostAttributeChange/PostAttributeBaseChange functions.
/**
* This is called just before any modification happens to an attribute's base value when an attribute aggregator exists.
* This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
* This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
* final value of the attribute actually changing.
*/
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
PostAttributeBaseChange
This function gets called after the base attribute value has been changed. I am not sure what sort of logic should be placed here, so any information anyone has on this would be super useful.
/** Called just after any modification happens to an attribute's base value when an attribute aggregator exists. */
virtual void PostAttributeBaseChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) const { }
PreGameplayEffectExecute
This function gets called before the Gameplay Effect is executed. I am not sure what sort of logic should be placed here, so any information anyone has on this would be super useful.
/**
* Called just before modifying the value of an attribute. AttributeSet can make additional modifications here. Return true to continue, or false to throw out the modification.
* Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data) { return true; }
PostGameplayEffectExecute
The PostGameplayEffectExecute function is called after the Gameplay Effect is executed and I use to to evaluate whether or not the data being evaluated involved my IncomingDamage attribute, and if so, send a Gameplay Event to the Owning Ability System Component to inform them of the owners’ death.
/**
* Called just before a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
* Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
void UGASCourseHealthAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if(Data.EvaluatedData.Attribute == GetIncomingDamageAttribute())
{
const float LocalDamage = GetIncomingDamage();
SetIncomingDamage(0.0f);
SetCurrentHealth(GetCurrentHealth() - LocalDamage);
if(GetCurrentHealth() <= 0.0f && !GetOwningAbilitySystemComponent()->HasMatchingGameplayTag(Status_Death))
{
FGameplayEventData OnDeathPayload;
OnDeathPayload.Instigator = Data.EffectSpec.GetContext().GetOriginalInstigator();
OnDeathPayload.Target = GetOwningActor();
OnDeathPayload.ContextHandle = Data.EffectSpec.GetContext();
OnDeathPayload.EventMagnitude = LocalDamage;
GetOwningAbilitySystemComponent()->HandleGameplayEvent(Event_OnDeath, &OnDeathPayload);
}
}
else if (Data.EvaluatedData.Attribute == GetCurrentHealthAttribute())
{
// Handle other health changes.
// Health loss should go through Damage.
SetCurrentHealth(FMath::Clamp(GetCurrentHealth(), 0.0f, GetMaxHealth()));
}
}
Mod Evaluation Channels
By default, when two or more Gameplay Effect modifiers of the same operator are applied to the same attribute, those modifiers are summed together to provide the final modification to the attribute. Here is the final formula applied for all modifiers:
((CurrentValue + Additive) * Multiplicitive) / Division
Mod Evaluation Channels allow designers a bit more control over which modifiers are summed together and which modifiers amplify each other. Modifiers using the same channel are combined using the default calculation described above, summed together, while modifiers of different channels will amplify each other. Here is an example:
When applying the following Gameplay Effects using the same evaluation channel, Weapons, here is the result:
-
GE_ModChannelTest_01
-
GE_ModChannelTest_02
Here is a break-down of what is happening with the calcuation to the attribute, OneAttribute in the above example:
- By default, GE_ModChannelTest_01 is applied to the player character, multipling 1.5f to the base attribute value 1.0f of OneAttribute.
- After 3 seconds, GE_ModChannelTest_02 adds 2.0f to the OneAttribute.
- The final result is 4.5f. [1.0f (Base Value) * 1.5f (GE_ModChannelTest_01) + 1.0f (Base Value) + 2.0f (GE_ModChannelTest_02)] OR [(1.0f * 1.5f) + (1.0f + 2.0f)] OR [1.5f + 3.0f]
Now what happens if we make GE_ModChannelTest_02 use a different evaluation channel, such as Equipment:
Here is a break-down of what is happening with the calcuation to the attribute, OneAttribute in the above example:
- By default, GE_ModChannelTest_01 is applied to the player character, multipling 1.5f to the base attribute value 1.0f of OneAttribute.
- After 3 seconds, GE_ModChannelTest_02 adds 2.0f to the OneAttribute, but now using the Equipment evaluation channel.
- The final result is 3.5f. [1.0f (Base Value) * 1.5f (GE_ModChannelTest_01) + 2.0f (GE_ModChannelTest_02)] OR [(1.0f * 1.5f) + 2.0f] OR [1.5f + 2.0f]
Mod Evaluation Channels can be added in the DefaultGame.ini
. Below is the example I have in place for my project, which is based off of the example provided here: GAS: Customize modifier aggregation with mod evaluation channels | Tutorial
[/Script/GameplayAbilities.AbilitySystemGlobals]
;Set up mod evaluation channels
bAllowGameplayModEvaluationChannels=True
GameplayModEvaluationChannelAliases[0]=Weapons
GameplayModEvaluationChannelAliases[1]=Equipment
GameplayModEvaluationChannelAliases[2]=Abilities
GameplayModEvaluationChannelAliases[3]=Difficulty
GameplayModEvaluationChannelAliases[4]=WorldEvents
GameplayModEvaluationChannelAliases[5]=GameMode
After adding these lines into the ini file, you will see these options appear when adding modifiers to your Gameplay Effects:
NOTE: Mod Evaluation Channels only appear for Gameplay Effects that use either Infinite or Duration based duration policies; Instant Gameplay Effects are not supported because modifiers are permanently affecting the Base Value and other modifiers won’t impact that change.
Attribute Sets
The Gameplay Ability System allows you to create as many Attribute Sets as you want, so it is up to developer whether or not to have one large monolithic Attribute Set that contains all attributes, or create separate Attribute Sets based on categories such as Health related attributes. For my GAS Course Project, I prefer creating separate Attribute Sets for different groups of Attributes so that I can allow opt-in functionality when it comes to granting Attributes to my actors. For example, the player character may require an Attribute Set for movement speeds while a static actor type, such as a tower in a MOBA game, would not need these types of Attributes, but both would require Health Attributes.
Adding Attribute Sets via Ability Sets
In my GAS Course Project, I use Ability Sets to grant Attribute Sets to my characters. Below is the code used to grant the Attribute Sets:
GASCourseGameplayAbilitySet.h
public:
void AddAttributeSet(UGASCourseAttributeSet* Set);
// Grants the ability set to the specified ability system component.
// The returned handles can be used later to take away anything that was granted.
void GiveToAbilitySystem(UGASCourseAbilitySystemComponent* ASC, FGASCourseAbilitySet_GrantedHandles* OutGrantedHandles, UObject* SourceObject = nullptr) const;
GASCourseGameplayAbilitySet.cpp
void FGASCourseAbilitySet_GrantedHandles::AddAttributeSet(UGASCourseAttributeSet* Set)
{
GrantedAttributeSets.Add(Set);
}
void UGASCourseGameplayAbilitySet::GiveToAbilitySystem(UGASCourseAbilitySystemComponent* ASC, FGASCourseAbilitySet_GrantedHandles* OutGrantedHandles, UObject* SourceObject) const
{
check(ASC);
if (!ASC->IsOwnerActorAuthoritative())
{
// Must be authoritative to give or take ability sets.
return;
}
// Grant the attribute sets.
for (int32 SetIndex = 0; SetIndex < GrantedAttributes.Num(); ++SetIndex)
{
const FGASCourseAbilitySet_AttributeSet& SetToGrant = GrantedAttributes[SetIndex];
if (!IsValid(SetToGrant.AttributeSet))
{
continue;
}
UGASCourseAttributeSet* NewSet = NewObject<UGASCourseAttributeSet>(ASC->GetOwner(), SetToGrant.AttributeSet);
ASC->AddAttributeSetSubobject(NewSet);
if (OutGrantedHandles)
{
OutGrantedHandles->AddAttributeSet(NewSet);
}
}
// Grant the gameplay abilities.
for (int32 AbilityIndex = 0; AbilityIndex < GrantedGameplayAbilities.Num(); ++AbilityIndex)
{
const FGASCourseAbilitySet_GameplayAbility& AbilityToGrant = GrantedGameplayAbilities[AbilityIndex];
if (!IsValid(AbilityToGrant.Ability))
{
continue;
}
UGASCourseGameplayAbility* AbilityCDO = AbilityToGrant.Ability->GetDefaultObject<UGASCourseGameplayAbility>();
FGameplayAbilitySpec AbilitySpec(AbilityCDO, AbilityToGrant.AbilityLevel);
AbilitySpec.SourceObject = SourceObject;
AbilitySpec.DynamicAbilityTags.AddTag(AbilityToGrant.InputTag);
const FGameplayAbilitySpecHandle AbilitySpecHandle = ASC->GiveAbility(AbilitySpec);
if (OutGrantedHandles)
{
OutGrantedHandles->AddAbilitySpecHandle(AbilitySpecHandle);
}
}
// Grant the gameplay effects.
for (int32 EffectIndex = 0; EffectIndex < GrantedGameplayEffects.Num(); ++EffectIndex)
{
const FGASCourseAbilitySet_GameplayEffect& EffectToGrant = GrantedGameplayEffects[EffectIndex];
if (!IsValid(EffectToGrant.GameplayEffect))
{
continue;
}
const UGameplayEffect* GameplayEffect = EffectToGrant.GameplayEffect->GetDefaultObject<UGameplayEffect>();
const FActiveGameplayEffectHandle GameplayEffectHandle = ASC->ApplyGameplayEffectToSelf(GameplayEffect, EffectToGrant.EffectLevel, ASC->MakeEffectContext());
if (OutGrantedHandles)
{
OutGrantedHandles->AddGameplayEffectHandle(GameplayEffectHandle);
}
}
}
Defining Attributes
Attributes can only be defined in C++ inside of the header file of your AttributeSet class. It is recommended to add the following block of code to the top of each AttributeSet header file as these macros will automatically generate getter, setter, and intiialization functions for your Attributes:
// Uses macros from AttributeSet.h
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName)
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName)
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName)
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName)
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
Here are some examples of these functions:
InitCurrentHealth(100.0f);
float GetHealth = GetCurrentHealth();
SetCurrentHealth(1000.0f);
Here is how I define my attributes, using the GASCourseHealthAttributeSet class as an example:
GASCourseHealthAttributeSet.h
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(BlueprintReadOnly, Category = "Character Health Attributes", ReplicatedUsing=OnRep_CurrentHealth)
FGameplayAttributeData CurrentHealth;
ATTRIBUTE_ACCESSORS(UGASCourseHealthAttributeSet, CurrentHealth)
UPROPERTY(BlueprintReadOnly, Category = "Character Health Attributes", ReplicatedUsing=OnRep_MaxHealth)
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UGASCourseHealthAttributeSet, MaxHealth)
UPROPERTY(BlueprintReadOnly, Category = "Damage")
FGameplayAttributeData IncomingDamage;
ATTRIBUTE_ACCESSORS(UGASCourseHealthAttributeSet, IncomingDamage)
protected:
UFUNCTION()
virtual void OnRep_CurrentHealth(const FGameplayAttributeData& OldCurrentHealth);
UFUNCTION()
virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
GASCourseHealthAttributeSet.cpp
void UGASCourseHealthAttributeSet::OnRep_CurrentHealth(const FGameplayAttributeData& OldCurrentHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGASCourseHealthAttributeSet, CurrentHealth, OldCurrentHealth);
}
void UGASCourseHealthAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UGASCourseHealthAttributeSet, MaxHealth, OldMaxHealth);
}
void UGASCourseHealthAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UGASCourseHealthAttributeSet, CurrentHealth, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UGASCourseHealthAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
}
Initializing Attributes - Instant Gameplay Effect
For my GAS Course Project, I have opted-in to using the Instant Gameplay Effect approach because this is the option that Epic recommends, and is the option that can be found in Tranek’s Sample project.
GE_DummyCharacter_Initter
Initializing Attributes - Data Tables
If you choose to intialize your Attributes through a Data Table, you need to use the struct FAttributeMetaData:
* DataTable that allows us to define meta data about attributes. Still a work in progress.
*/
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FAttributeMetaData : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
public:
FAttributeMetaData();
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Attribute")
float BaseValue;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Attribute")
float MinValue;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Attribute")
float MaxValue;
UPROPERTY()
FString DerivedAttributeInfo;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Attribute")
bool bCanStack;
};
On a previous personal project, Project Hero, I had used the Data Table approach for initializing my characters’ attributes:
Name,BaseValue,MinValue,MaxValue,DerivedAttributeInfo,bCanStack
HeroCharacterAttributes.HeroCharacterMovementSpeed,400,0,10000,TRUE
HeroCharacterAttributes.HeroCharacterMovementSpeedMultiplier,1,1,10,TRUE
HeroCharacterAttributes.HeroCharacterJumpHeight,600,0,2000,TRUE
HeroCharacterAttributes.HeroCharacterJumpHeightMultiplier,1,0,10,TRUE
HeroCharacterAttributes.HeroCharacterAirControl,0.5,0.1,10,TRUE
HeroCharacterAttributes.HeroCharacterCrouchSpeed,200,0,10000,TRUE
HeroCharacterAttributes.HeroCharacterGroundFriction,8,0,10,TRUE
HeroCharacterAttributes.HeroCharacterForwardSpeedReduction,1,0,1,FALSE
HeroCharacterAttributes.HeroCharacterBackwardSpeedReduction,0.6,0,1,FALSE
HeroCharacterAttributes.HeroCharacterLateralSpeedReduction,0.8,0,1,FALSE
HeroCharacterAttributes.HeroCharacterForwardSpeedReductionBlocking,0.8,0,1,FALSE
HeroCharacterAttributes.HeroCharacterBackwardSpeedReductionBlocking,0.4,0,1,FALSE
HeroCharacterAttributes.HeroCharacterLateralSpeedReductionBlocking,0.6,0,1,FALSE
HeroCharacterAttributes.MaxHealth,100,0,10000,TRUE
HeroCharacterAttributes.HeroCharacterLevel,1,1,100,TRUE
HeroCharacterAttributes.HeroCharacterMaxLevel,100,1,100,TRUE
HeroCharacterAttributes.HealthRegeneration,1,0,100,TRUE
HeroCharacterAttributes.HealthRegenerationActivationDelay,1.5,0.01,10,TRUE
HeroCharacterAttributes.MaxMana,100,0,10000,TRUE
HeroCharacterAttributes.ManaRegeneration,1.5,0,100,TRUE
HeroCharacterAttributes.ManaRegenerationActivationDelay,1.2,0.01,10,TRUE
AHeroPlayerState::AHeroPlayerState()
{
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AttributeSetBase = CreateDefaultSubobject<UHeroCharacterAttributes>(TEXT("AttributeSetBase"));
static ConstructorHelpers::FObjectFinder<UDataTable> HeroAttributesInitialization(TEXT("/Game/Hero/DataTables/PlayerAttributesInitialization/Hero_Attributes_Initialization"));
PlayerAttributeSet = HeroAttributesInitialization.Object;
static ConstructorHelpers::FObjectFinder<UDataTable> HeroCooldownAttributesInitialization(TEXT("/Game/Hero/DataTables/PlayerAttributesInitialization/Hero_AbilityCooldownMultiplier_AttributesInitialization"));
CooldownAttributeSetDataTable = HeroCooldownAttributesInitialization.Object;
}
void AHeroPlayerState::InitializeAttributes()
{
if (AbilitySystemComponent && PlayerAttributeSet)
{
const UAttributeSet* Attributes = AbilitySystemComponent->InitStats(UHeroCharacterAttributes::StaticClass(), PlayerAttributeSet);
}
}
void AHeroCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (NewController->IsLocalPlayerController())
{
AHeroPlayerState* PS = GetPlayerState<AHeroPlayerState>();
if (PS)
{
// Set the ASC on the Server. Clients do this in OnRep_PlayerState()
AbilitySystemComponent = Cast<UAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// AI won't have PlayerControllers so we can init again here just to be sure. No harm in initing twice for heroes that have PlayerControllers.
PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
// Set the AttributeSetBase for convenience attribute functions
BaseHeroAttributes = PS->GetAttributeSetBase();
PS->InitializeAttributes();
AbilityCooldownAttributeSet = PS->GetCooldownAttributeSet();
PS->InitializeCooldownAttributeSet();
}
}
}
References:
https://www.thegames.dev/?p=119
Thank you for taking the time to read this post, and I hope you were able to take something away from it. Please let me know if I got any information wrong, or explained something incorrectly! Also add any thoughts or questions or code review feedback so we can all learn together I will be taking a break from making additional posts in this topic for the holidays, but I’ll be back in a few weeks with more!
Next Blog Post Topic:
*Gameplay Effect UI Data for Status Effects