Attribute Set Code Generation

Learn about the GenericGameplayAttribute module of GGA, which is used to expand the GameplayAttribute part of the GameplayAbilitySystem.

Cover.GGA
罗传月武(YueWu)

Author

罗传月武(YueWu)

专注游戏开发和Web技术。

Created: Apr 13, 2025Updated: Aug 03, 2026

Table of contents

Overview

GGA also has another code module called GenericGameplayAttribute, which is responsible for expanding the GameplayAttribute part of the GAS system and includes some built-in GameplayAttributes, also explained how the AttributeSet code generation works.

This module is very lightweight.

Attribute and Tag Mapping

GAS very much embraces GameplayTags, however, GameplayAttribute is not driven by GameplayTag, for this reason, GGA provides an optional mapping of GameplayAttribute to GameplayTag.

gga gen 1

You can get Attribute through Tag and you can get Tag through Attribute.

You can also register Attribute to a Tag at runtime (this operation is global).

Built-in Attributes

The GenericGameplayAttributes module comes with three commonly used attribute sets, AS_Health, AS_Mana, and AS_Stamina.

gga gen 2

You can use it or not. It is abstract enough and is automatically generated (see below).

How to design an AttributeSet

If you refer to various examples and tutorials on the web, you will find that people are used to using one AttributeSet to define all the attributes used in the game, and they will write a lot of gameplay logic in the AttributeSet, which I don't think is desirable.

Instead, I think AttributeSet should be divided according to the function in the game, and just play the role of data definition. It doesn't need to contain gameplay logic, and only needs to be processed externally according to the logic of attribute changes.

Assuming your game requires two attributes, Health, and Stamina, you should define two AttributeSets.

AS_Health(attributes related to health)

Column 1Column 2

Health

Current value

MaxHealth

Max value

Healing

Value to add

Damage

Value to reduce

AS_Stamina(attributes related to stamina)

Column 1Column 2

Stamina

Current value

MaxStamina

Max value

Healing

Value to add

Damage

Value to reduce

The advantages of doing this are:

  1. In multiplayer games, different characters may need different attributes. In order to avoid a lot of network replication costs, you should only give the set of attributes needed by the characters that need it.
  2. At the same time, this modular design can go far in the future. (For example, if you use GameFeature, and one of your subFeatures defines its dedicated set of attributes)
Don't abuse GameplayAttributes. Here's a great post that teaches you how to use a small number of attributes and accomplish complex tasks.

About Code Generation

GGA provides an code generation funationality that allows you to generate C++ AttributeSet codes.

Even for C++ users, adding new attributes can be a very tedious process that requires a lot of code to be written by hand.

In order to use code generation, you must install visual studio on windows or XCode on your mac by following official documentation, and then convert your project into a C++ project.

Code Generation Guide

GGA provides code generation functionality to help you generate C++ AttributeSet code. Even for C++ users, adding new attributes is a very tedious process, requiring a lot of boilerplate code to be written manually. To use the code generation feature, you need to install Visual Studio on Windows or Xcode on Mac (please refer to the official documentation) and convert your project into a code project.

GGA.属性集定义

Steps to add a new attribute set:

  1. Create a new attribute set definition asset: Right-click in the blank area of ​​the project browser -> GenericGameplayAbilities -> CoreConfig -> AttributeSetDefinition.
  2. Name the new attribute set asset: It is recommended to name the new attribute set definition with the prefix ASDefinition_, such as ASDefinition_Health|Combat|Stamina.
  3. Configure code generation: Specify which project module and path the code will be generated in, the code file name, the attributes it must have, etc. You can also select a threshold template from the template drop-down list to quickly fill in the configuration.
  4. Click the Build/Rebuild button. After the code is generated, you need to recompile the project.

Note

You can add other allowed code subpaths in Project Settings -> GGA Attribute Set Generator settings, such as "MyFolder/Attributes". By default, only the two folders, module name/Attributes|AttributeSets, are allowed.

Generated Code

Here is an example of generating gameplay attribute code from json.

Above AttributeSetDefinition generates the following code:

cpp
1// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
2
3#pragma once
4
5#include "CoreMinimal.h"
6#include "AttributeSet.h"
7#include "AbilitySystemComponent.h"
8#include "NativeGameplayTags.h"
9
10#include "AS_Combat.generated.h"
11
12namespace AS_Combat
13{
14
15 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Damage)
16
17 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DamageNegation)
18
19 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GuardDamageNegation)
20
21 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(StaminaDamage)
22
23 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(StaminaDamageNegation)
24
25
26}
27
28#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
29GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
30GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
31GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
32GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
33
34UCLASS()
35class GENERICGAMEPLAYATTRIBUTES_API UAS_Combat : public UAttributeSet
36{
37 GENERATED_BODY()
38
39
40public:
41
42 UAS_Combat();
43
44 virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
45
46 virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
47
48 virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
49
50 virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;
51
52 virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
53
54 // The damage that will apply to target
55 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Damage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
56 FGameplayAttributeData Damage{ 0.0 };
57 ATTRIBUTE_ACCESSORS(ThisClass, Damage)
58
59 // The damage reduction(percentage) for incoming health damage
60 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_DamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
61 FGameplayAttributeData DamageNegation{ 0.0 };
62 ATTRIBUTE_ACCESSORS(ThisClass, DamageNegation)
63
64 // The damage reduction(percentage) for incoming health damage while guarding
65 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_GuardDamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
66 FGameplayAttributeData GuardDamageNegation{ 0.0 };
67 ATTRIBUTE_ACCESSORS(ThisClass, GuardDamageNegation)
68
69 // The stamina damage that will apply to target
70 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaDamage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
71 FGameplayAttributeData StaminaDamage{ 0.0 };
72 ATTRIBUTE_ACCESSORS(ThisClass, StaminaDamage)
73
74 // The damage reduction(percentage) for incoming stamina damage
75 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaDamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
76 FGameplayAttributeData StaminaDamageNegation{ 0.0 };
77 ATTRIBUTE_ACCESSORS(ThisClass, StaminaDamageNegation)
78
79
80
81
82
83 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetDamageAttribute"), Category = "Attribute|CombatSet")
84 static FGameplayAttribute Bp_GetDamageAttribute();
85
86 UFUNCTION(BlueprintPure,meta=(DisplayName="GetDamage"), Category = "Attribute|CombatSet")
87 float Bp_GetDamage() const;
88
89 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetDamage"), Category = "Attribute|CombatSet")
90 void Bp_SetDamage(float NewValue);
91
92 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitDamage"), Category = "Attribute|CombatSet")
93 void Bp_InitDamage(float NewValue);
94
95
96 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetDamageNegationAttribute"), Category = "Attribute|CombatSet")
97 static FGameplayAttribute Bp_GetDamageNegationAttribute();
98
99 UFUNCTION(BlueprintPure,meta=(DisplayName="GetDamageNegation"), Category = "Attribute|CombatSet")
100 float Bp_GetDamageNegation() const;
101
102 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetDamageNegation"), Category = "Attribute|CombatSet")
103 void Bp_SetDamageNegation(float NewValue);
104
105 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitDamageNegation"), Category = "Attribute|CombatSet")
106 void Bp_InitDamageNegation(float NewValue);
107
108
109 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetGuardDamageNegationAttribute"), Category = "Attribute|CombatSet")
110 static FGameplayAttribute Bp_GetGuardDamageNegationAttribute();
111
112 UFUNCTION(BlueprintPure,meta=(DisplayName="GetGuardDamageNegation"), Category = "Attribute|CombatSet")
113 float Bp_GetGuardDamageNegation() const;
114
115 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetGuardDamageNegation"), Category = "Attribute|CombatSet")
116 void Bp_SetGuardDamageNegation(float NewValue);
117
118 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitGuardDamageNegation"), Category = "Attribute|CombatSet")
119 void Bp_InitGuardDamageNegation(float NewValue);
120
121
122 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetStaminaDamageAttribute"), Category = "Attribute|CombatSet")
123 static FGameplayAttribute Bp_GetStaminaDamageAttribute();
124
125 UFUNCTION(BlueprintPure,meta=(DisplayName="GetStaminaDamage"), Category = "Attribute|CombatSet")
126 float Bp_GetStaminaDamage() const;
127
128 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetStaminaDamage"), Category = "Attribute|CombatSet")
129 void Bp_SetStaminaDamage(float NewValue);
130
131 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitStaminaDamage"), Category = "Attribute|CombatSet")
132 void Bp_InitStaminaDamage(float NewValue);
133
134
135 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetStaminaDamageNegationAttribute"), Category = "Attribute|CombatSet")
136 static FGameplayAttribute Bp_GetStaminaDamageNegationAttribute();
137
138 UFUNCTION(BlueprintPure,meta=(DisplayName="GetStaminaDamageNegation"), Category = "Attribute|CombatSet")
139 float Bp_GetStaminaDamageNegation() const;
140
141 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetStaminaDamageNegation"), Category = "Attribute|CombatSet")
142 void Bp_SetStaminaDamageNegation(float NewValue);
143
144 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitStaminaDamageNegation"), Category = "Attribute|CombatSet")
145 void Bp_InitStaminaDamageNegation(float NewValue);
146
147
148
149
150protected:
151
152 /** 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) */
153 virtual void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);
154
155 UFUNCTION()
156 virtual void OnRep_Damage(const FGameplayAttributeData& OldValue);
157
158 UFUNCTION()
159 virtual void OnRep_DamageNegation(const FGameplayAttributeData& OldValue);
160
161 UFUNCTION()
162 virtual void OnRep_GuardDamageNegation(const FGameplayAttributeData& OldValue);
163
164 UFUNCTION()
165 virtual void OnRep_StaminaDamage(const FGameplayAttributeData& OldValue);
166
167 UFUNCTION()
168 virtual void OnRep_StaminaDamageNegation(const FGameplayAttributeData& OldValue);
169
170};

As you can see, just a simple set of attributes requires so much code. So code generation is given!

Generated Blueprints

gga gen 3
gga gen 4
gga gen 5

Some thoughts

Since the attribute system of GAS has to be done in C++, this has hindered a large portion of blueprint users, while various solutions for blueprint users have appeared in Unreal Marketplace. Even a pure blueprint GameplayAttribute solution appeared.

However, I think that if you take a big detour to use GameplayAttribute with all kinds of hacks just because you have a fear of code projects, I think you are putting the cart before the horse.

What if in the future Unreal comes with blueprint support for GameplayAttribute?

What if in the future the Unreal team makes it so that GAS no longer requires C++?

Of course, this is just my own personal opinion. If you're going to develop serious games, you're always going to be faced with C++.