概述
GGA同时还有另外一个代码模块,叫GenericGameplayAttribute。它主要负责对GAS系统中GameplayAttribute这块进行一定拓展,并包含了一些内置的GameplayAttributeSet 以及 AttributeSet代码生成器。这个模块非常轻量。
属性与Tag映射
GAS非常拥抱GameplayTags,然而GameplayAttribute并不是由GameplayTag驱动的,为此,GGA提供了可选的GameplayAttribute到GameplayTag的映射功能。

你可以通过Tag拿到Attribute,你也可以通过Attribute拿到Tag。
你也可以在运行时注册Attribute到某个Tag(这个操作是全局的)。
内置属性
GenericGameplayAttributes模块自带了三个比较常用的属性集,分别是AS_Health,AS_Mana,以及AS_Stamina。

你可以使用它,也可以不使用。它足够抽象,且是自动生成的(见下文)。
如何设计AttributeSet
如果你参考网络上的各种案例,教程,你会发现,大家习惯于使用一个AttributeSet,把游戏中所有用到的属性都定义在其中,而且会在AttributeSet中写很多游戏性逻辑,我认为这是不可取的。
相反,我认为AttributeSet应该根据游戏中的功能进行划分,且仅仅是起数据定义作用,不需要包含游戏逻辑,只需要在外部根据属性变化进行逻辑处理。
假定你的游戏中,需要Health,和Stamina两种属性,你应该定义两个AttributeSet。
AS_Health(生命属性集)
| Column 1 | Column 2 |
|---|---|
Health | 当前血量 |
MaxHealth | 最大血量 |
Healing | 要恢复的血量 |
Damage | 要扣除的血量 |
AS_Stamina(体力属性集)
| Column 1 | Column 2 |
|---|---|
Stamina | 当前体力 |
MaxStamina | 最大体力 |
Healing | 要恢复的体力 |
Damage | 要扣除的体力 |
这样做的好处是:
- 在多人游戏中,不同的角色可能需要的属性是不一样的,为了避免大量的网络同步消耗,你应该只给需要的角色所需的属性集。
- 同时这种模块化的设计,能够在未来走得更远。(比如你使用GameFeature,你的某一个子Feature定义了它专用的属性集)
不要滥用GameplayAttributes,这里有一个很棒的文章教你如何用少量的属性,完成复杂的任务。
关于代码生成
GGA提供了代码生成功能帮助你生成C++的AttributeSet代码。
即便是对于C++用户来说,添加新属性也是一个非常繁琐的过程,需要手写很多样板代码。
要使用代码生成功能,你需要安装Windows平台上的VisualStudio或者Mac平台上的XCode(请参考官方文档),并将你的项目转换成代码项目。
代码生成指南

新增属性集的步骤:
- 创建新的属性集定义资产:在项目浏览器的空白处右键->GenericGameplayAbilities->CoreConfig->AttributeSetDefinition
- 命名新的属性集资产:建议以ASDefinition_为前缀命名新建的属性集定义,比如ASDefinition_Health|Combat|Stamina
- 进行代码生成配置:指定代码在哪个项目模块及其路径中生成,代码文件名,具备的属性等,也可以从模板下拉列表中选择阈值的模板帮你快速填充配置。
- 点击生成/重新生成按钮,代码生成完毕后,需要重新编译项目。
Note
你可以在项目设置->GGA Attribute Set Generator设置中,添加其它的可允许的代码子路径列表,比如“MyFolder/Attributes”,默认情况下,只允许模块名/Attributes|AttributeSets两个文件夹。
生成的代码
这是上面的配置会生成的代码:
1// Copyright 2025 https://yuewu.dev/en All Rights Reserved.23#pragma once45#include "CoreMinimal.h"6#include "AttributeSet.h"7#include "AbilitySystemComponent.h"8#include "NativeGameplayTags.h"910#include "AS_Combat.generated.h"1112namespace AS_Combat13{1415 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Damage)1617 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DamageNegation)1819 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GuardDamageNegation)2021 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(StaminaDamage)2223 GENERICGAMEPLAYATTRIBUTES_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(StaminaDamageNegation)242526}2728#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \29GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \30GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \31GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \32GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)3334UCLASS()35class GENERICGAMEPLAYATTRIBUTES_API UAS_Combat : public UAttributeSet36{37 GENERATED_BODY()383940public:4142 UAS_Combat();4344 virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;4546 virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;4748 virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;4950 virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;5152 virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;5354 // The damage that will apply to target55 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Damage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))56 FGameplayAttributeData Damage{ 0.0 };57 ATTRIBUTE_ACCESSORS(ThisClass, Damage)5859 // The damage reduction(percentage) for incoming health damage60 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_DamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))61 FGameplayAttributeData DamageNegation{ 0.0 };62 ATTRIBUTE_ACCESSORS(ThisClass, DamageNegation)6364 // The damage reduction(percentage) for incoming health damage while guarding65 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_GuardDamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))66 FGameplayAttributeData GuardDamageNegation{ 0.0 };67 ATTRIBUTE_ACCESSORS(ThisClass, GuardDamageNegation)6869 // The stamina damage that will apply to target70 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaDamage, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))71 FGameplayAttributeData StaminaDamage{ 0.0 };72 ATTRIBUTE_ACCESSORS(ThisClass, StaminaDamage)7374 // The damage reduction(percentage) for incoming stamina damage75 UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaDamageNegation, Category = "Attribute|CombatSet", Meta = (AllowPrivateAccess = true))76 FGameplayAttributeData StaminaDamageNegation{ 0.0 };77 ATTRIBUTE_ACCESSORS(ThisClass, StaminaDamageNegation)787980818283 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetDamageAttribute"), Category = "Attribute|CombatSet")84 static FGameplayAttribute Bp_GetDamageAttribute();8586 UFUNCTION(BlueprintPure,meta=(DisplayName="GetDamage"), Category = "Attribute|CombatSet")87 float Bp_GetDamage() const;8889 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetDamage"), Category = "Attribute|CombatSet")90 void Bp_SetDamage(float NewValue);9192 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitDamage"), Category = "Attribute|CombatSet")93 void Bp_InitDamage(float NewValue);949596 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetDamageNegationAttribute"), Category = "Attribute|CombatSet")97 static FGameplayAttribute Bp_GetDamageNegationAttribute();9899 UFUNCTION(BlueprintPure,meta=(DisplayName="GetDamageNegation"), Category = "Attribute|CombatSet")100 float Bp_GetDamageNegation() const;101102 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetDamageNegation"), Category = "Attribute|CombatSet")103 void Bp_SetDamageNegation(float NewValue);104105 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitDamageNegation"), Category = "Attribute|CombatSet")106 void Bp_InitDamageNegation(float NewValue);107108109 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetGuardDamageNegationAttribute"), Category = "Attribute|CombatSet")110 static FGameplayAttribute Bp_GetGuardDamageNegationAttribute();111112 UFUNCTION(BlueprintPure,meta=(DisplayName="GetGuardDamageNegation"), Category = "Attribute|CombatSet")113 float Bp_GetGuardDamageNegation() const;114115 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetGuardDamageNegation"), Category = "Attribute|CombatSet")116 void Bp_SetGuardDamageNegation(float NewValue);117118 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitGuardDamageNegation"), Category = "Attribute|CombatSet")119 void Bp_InitGuardDamageNegation(float NewValue);120121122 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetStaminaDamageAttribute"), Category = "Attribute|CombatSet")123 static FGameplayAttribute Bp_GetStaminaDamageAttribute();124125 UFUNCTION(BlueprintPure,meta=(DisplayName="GetStaminaDamage"), Category = "Attribute|CombatSet")126 float Bp_GetStaminaDamage() const;127128 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetStaminaDamage"), Category = "Attribute|CombatSet")129 void Bp_SetStaminaDamage(float NewValue);130131 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitStaminaDamage"), Category = "Attribute|CombatSet")132 void Bp_InitStaminaDamage(float NewValue);133134135 UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetStaminaDamageNegationAttribute"), Category = "Attribute|CombatSet")136 static FGameplayAttribute Bp_GetStaminaDamageNegationAttribute();137138 UFUNCTION(BlueprintPure,meta=(DisplayName="GetStaminaDamageNegation"), Category = "Attribute|CombatSet")139 float Bp_GetStaminaDamageNegation() const;140141 UFUNCTION(BlueprintCallable,meta=(DisplayName="SetStaminaDamageNegation"), Category = "Attribute|CombatSet")142 void Bp_SetStaminaDamageNegation(float NewValue);143144 UFUNCTION(BlueprintCallable,meta=(DisplayName="InitStaminaDamageNegation"), Category = "Attribute|CombatSet")145 void Bp_InitStaminaDamageNegation(float NewValue);146147148149150protected:151152 /** 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);154155 UFUNCTION()156 virtual void OnRep_Damage(const FGameplayAttributeData& OldValue);157158 UFUNCTION()159 virtual void OnRep_DamageNegation(const FGameplayAttributeData& OldValue);160161 UFUNCTION()162 virtual void OnRep_GuardDamageNegation(const FGameplayAttributeData& OldValue);163164 UFUNCTION()165 virtual void OnRep_StaminaDamage(const FGameplayAttributeData& OldValue);166167 UFUNCTION()168 virtual void OnRep_StaminaDamageNegation(const FGameplayAttributeData& OldValue);169170};
如你所见,只是一个简单的属性集,就需要这么多代码。所以代码生成是必然的选择!
生成的蓝图



一些思考
由于GAS的属性系统必须在C++完成,所以这阻碍了很大一部分的蓝图用户,同时虚幻商城中也出现了各种针对蓝图用户的解决方案。甚至出现了纯蓝图的GameplayAttribute解决方案。
但我认为,如果只是因为对代码项目产生恐惧,而绕了一个大弯去用各种黑科技来使用GameplayAttribute,我认为是本末倒置了。万一未来虚幻自带了GameplayAttribute的蓝图支持?万一未来虚幻团队让GAS不再必须使用C++?
