🚀 Spring Sale is live! 🚀 Don't miss your chance to get all my products at 30% OFF!  

Get it!

Attribute Set Code Generation

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. This module is very light.

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)

Health

Current value

MaxHealth

Max value

Healing

Value to add

Damage

Value to reduce

AS_Stamina(attributes related to stamina)

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.

Adding new attributes (code generator)

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

GGA provides an additional command line tool that allows you to generate C++ AttributeSet code for you by writing simple Json.

You don't need to write the code by hand, you just need to convert your project into a code project.

Join my Discord server and go to #GGA-Resources to download the Code Generator.

Video Demo

Generation Example

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

1{
2 "ModuleName": "GENERICGAMEPLAYATTRIBUTES",
3 "ClassName": "AS_Combat",
4 "EnableGGA": true,
5 "TagPrefix": "GGF.Attribute.CombatSet.",
6 "Category": "Attribute|CombatSet",
7 "FileName": "AS_Combat",
8 "CopyRight": "// Copyright 2024 https://yuewu.dev/en All Rights Reserved.",
9 "HeaderOutputDirectory": "E:/GenericGame5.4/Plugins/GenericGameplayAbilities/Source/GenericGameplayAttributes/Public/Attributes",
10 "CppOutputDirectory": "E:/GenericGame5.4/Plugins/GenericGameplayAbilities/Source/GenericGameplayAttributes/Private/Attributes",
11 "HeaderIncludes": [],
12 "CppIncludes": [
13 "Attributes/AS_Combat.h"
14 ],
15 "Attributes": [
16 {
17 "Name": "Damage",
18 "Default": 0,
19 "Comment": "The damage that will apply to target"
20 },
21 {
22 "Name": "DamageNegation",
23 "Default": 0,
24 "Comment": "The damage reduction(percentage) for incoming health damage"
25 },
26 {
27 "Name": "StaminaDamage",
28 "Default": 0,
29 "Comment": "The stamina damage that will apply to target"
30 },
31 {
32 "Name": "StaminaDamageNegation",
33 "Default": 0,
34 "Comment": "The damage reduction(percentage) for incoming stamina damage"
35 },
36 {
37 "Name": "PostureDamage",
38 "Default": 0,
39 "Comment": "The posture(stance) damage that will apply to target"
40 },
41 {
42 "Name": "PostureDamageNegation",
43 "Default": 0,
44 "Comment": "The damage reduction(percentage) for incoming posture damage"
45 }
46 ]
47}

The Json above generates the following code:

1// Copyright 2024 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(StaminaDamage)
20
21 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(StaminaDamageNegation)
22
23 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(PostureDamage)
24
25 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(PostureDamageNegation)
26
27
28}
29
30#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
31GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
32GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
33GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
34GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
35
36UCLASS()
37class GENERICGAMEPLAYATTRIBUTES_API UAS_Combat : public UAttributeSet
38{
39 GENERATED_BODY()
40
41
42public:
43
44 UAS_Combat();
45
46 virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
47
48 virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
49
50 virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
51
52 // The damage that will apply to target
53 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Damage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
54 FGameplayAttributeData Damage{ 0.0 };
55 ATTRIBUTE_ACCESSORS(ThisClass, Damage)
56
57 // The damage reduction(percentage) for incoming health damage
58 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_DamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
59 FGameplayAttributeData DamageNegation{ 0.0 };
60 ATTRIBUTE_ACCESSORS(ThisClass, DamageNegation)
61
62 // The stamina damage that will apply to target
63 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaDamage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
64 FGameplayAttributeData StaminaDamage{ 0.0 };
65 ATTRIBUTE_ACCESSORS(ThisClass, StaminaDamage)
66
67 // The damage reduction(percentage) for incoming stamina damage
68 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaDamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
69 FGameplayAttributeData StaminaDamageNegation{ 0.0 };
70 ATTRIBUTE_ACCESSORS(ThisClass, StaminaDamageNegation)
71
72 // The posture(stance) damage that will apply to target
73 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_PostureDamage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
74 FGameplayAttributeData PostureDamage{ 0.0 };
75 ATTRIBUTE_ACCESSORS(ThisClass, PostureDamage)
76
77 // The damage reduction(percentage) for incoming posture damage
78 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_PostureDamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))
79 FGameplayAttributeData PostureDamageNegation{ 0.0 };
80 ATTRIBUTE_ACCESSORS(ThisClass, PostureDamageNegation)
81
82 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetDamageAttribute"), Category = "Attribute|CombatSet")
83 static FGameplayAttribute Bp_GetDamageAttribute();
84
85 UFUNCTION(BlueprintPure,meta=(DisplayName="GetDamage"), Category = "Attribute|CombatSet")
86 float Bp_GetDamage() const;
87
88 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetDamage"), Category = "Attribute|CombatSet")
89 void Bp_SetDamage(float NewValue);
90
91 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitDamage"), Category = "Attribute|CombatSet")
92 void Bp_InitDamage(float NewValue);
93
94 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetDamageNegationAttribute"), Category = "Attribute|CombatSet")
95 static FGameplayAttribute Bp_GetDamageNegationAttribute();
96
97 UFUNCTION(BlueprintPure,meta=(DisplayName="GetDamageNegation"), Category = "Attribute|CombatSet")
98 float Bp_GetDamageNegation() const;
99
100 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetDamageNegation"), Category = "Attribute|CombatSet")
101 void Bp_SetDamageNegation(float NewValue);
102
103 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitDamageNegation"), Category = "Attribute|CombatSet")
104 void Bp_InitDamageNegation(float NewValue);
105
106 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetStaminaDamageAttribute"), Category = "Attribute|CombatSet")
107 static FGameplayAttribute Bp_GetStaminaDamageAttribute();
108
109 UFUNCTION(BlueprintPure,meta=(DisplayName="GetStaminaDamage"), Category = "Attribute|CombatSet")
110 float Bp_GetStaminaDamage() const;
111
112 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetStaminaDamage"), Category = "Attribute|CombatSet")
113 void Bp_SetStaminaDamage(float NewValue);
114
115 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitStaminaDamage"), Category = "Attribute|CombatSet")
116 void Bp_InitStaminaDamage(float NewValue);
117
118 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetStaminaDamageNegationAttribute"), Category = "Attribute|CombatSet")
119 static FGameplayAttribute Bp_GetStaminaDamageNegationAttribute();
120
121 UFUNCTION(BlueprintPure,meta=(DisplayName="GetStaminaDamageNegation"), Category = "Attribute|CombatSet")
122 float Bp_GetStaminaDamageNegation() const;
123
124 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetStaminaDamageNegation"), Category = "Attribute|CombatSet")
125 void Bp_SetStaminaDamageNegation(float NewValue);
126
127 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitStaminaDamageNegation"), Category = "Attribute|CombatSet")
128 void Bp_InitStaminaDamageNegation(float NewValue);
129
130
131 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetPostureDamageAttribute"), Category = "Attribute|CombatSet")
132 static FGameplayAttribute Bp_GetPostureDamageAttribute();
133
134 UFUNCTION(BlueprintPure,meta=(DisplayName="GetPostureDamage"), Category = "Attribute|CombatSet")
135 float Bp_GetPostureDamage() const;
136
137 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetPostureDamage"), Category = "Attribute|CombatSet")
138 void Bp_SetPostureDamage(float NewValue);
139
140 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitPostureDamage"), Category = "Attribute|CombatSet")
141 void Bp_InitPostureDamage(float NewValue);
142
143 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetPostureDamageNegationAttribute"), Category = "Attribute|CombatSet")
144 static FGameplayAttribute Bp_GetPostureDamageNegationAttribute();
145
146 UFUNCTION(BlueprintPure,meta=(DisplayName="GetPostureDamageNegation"), Category = "Attribute|CombatSet")
147 float Bp_GetPostureDamageNegation() const;
148
149 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetPostureDamageNegation"), Category = "Attribute|CombatSet")
150 void Bp_SetPostureDamageNegation(float NewValue);
151
152 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitPostureDamageNegation"), Category = "Attribute|CombatSet")
153 void Bp_InitPostureDamageNegation(float NewValue);
154
155protected:
156
157 /** 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) */
158 virtual void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);
159
160 UFUNCTION()
161 virtual void OnRep_Damage(const FGameplayAttributeData& OldValue);
162
163 UFUNCTION()
164 virtual void OnRep_DamageNegation(const FGameplayAttributeData& OldValue);
165
166 UFUNCTION()
167 virtual void OnRep_StaminaDamage(const FGameplayAttributeData& OldValue);
168
169 UFUNCTION()
170 virtual void OnRep_StaminaDamageNegation(const FGameplayAttributeData& OldValue);
171
172 UFUNCTION()
173 virtual void OnRep_PostureDamage(const FGameplayAttributeData& OldValue);
174
175 UFUNCTION()
176 virtual void OnRep_PostureDamageNegation(const FGameplayAttributeData& OldValue);
177};

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

Generated Bluepirnt Nodes

gga gen 3
gga gen 4
gga gen 5

A guide to the code generator

It's just a simple executable (gga.exe) written in Golang. Users only need to know about how to use it (Tools's source code will available soon), the code it generates is code after all, and if you're not happy with it you can still modify it.

Convert project to C++

Make sure to convert the project into a C++ project and add plugin related dependencies, I will add a more detailed tutorial about this part later.

Defining json and generating the code

Let's assume you have a directory containing gga.exe, and one configured Json.

gga gen 6

The ‘ModuleName’ must be in all capitals, and don't forget to add ‘EnableGGA’: true.

Simply open CMD (Win10) or Terminal (Win11) in the directory where gga.exe is located and type in the following commands.

./gga.exe gen -f ./Sample.json

Then you should recompile your project.

Note: . / is the current path, gga.exe is the generator, gen means that the operation is a generate operation, and the -f option specifies the file to be generated. The -f option specifies the file to be generated, and /xxx.json is the actual property definition.

Modify Code generation template

gga gen 7

As you can see, these two files are templates used to generate code. You can also modify it to add additional code (Just don't break the if/else/for in template files. )

Future plans to code generator

I have a plan to integrate code generator into unreal editor, You can configure your attribute set with data table and generate code via asset action.

gga gen 8
gga gen 9

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++.