文章目录
前言
现在深入分析 GAS 中 Attribute System 的底层实现原理,包括:
-
AttributeSet 的作用和结构
-
FGameplayAttribute 的底层机制
-
属性是如何被修改的
-
多人同步的技术原理
-
GAS 如何保证属性安全性、灵活性与效率
一、Attribute System 架构概览
Attribute System 是 GAS 的核心基础,用于统一管理角色的数值属性(如血量、攻击力、护甲、速度等)。
核心类关系:
UAttributeSet // 属性集合类,定义具体属性
↑
FGameplayAttribute // 属性引用结构,用于泛型操作
↑
UAbilitySystemComponent // 管理所有 AttributeSet + 与 GE 交互
它并不直接处理逻辑,而是 通过 ASC、GE、Tag 等统一修改和同步数值属性。
二、UAttributeSet:属性类本体
UAttributeSet 是一个继承自 UObject 的类,开发者可自定义子类,在其中定义属性字段:
UCLASS()
class UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldHealth);
static FGameplayAttribute GetHealthAttribute();
};
-
说明:
-
每个属性都是 FGameplayAttributeData 类型
该类型封装了 float 值,支持缓冲、同步、变动检测等
可通过 OnRep_XXX() 实现属性同步的回调
三、FGameplayAttributeData 的作用
它是 GAS 中实际存储属性值的类型:
struct FGameplayAttributeData
{
float BaseValue;
float CurrentValue;
};
-
特性:
-
BaseValue:基础值,不随 BUFF 等效果改变
CurrentValue:实际数值,会受 GE 动态影响
内建了属性变更检测与 NetDeltaSerialize 支持
🧬 四、FGameplayAttribute:属性引用包装器
为了让 GE、GA 以“泛型方式”修改属性(而不是硬编码字段名),GAS 提供了 FGameplayAttribute:
FGameplayAttribute MyAttribute = UMyAttributeSet::GetHealthAttribute();
ASC->SetNumericAttributeBase(MyAttribute, 100.0f);
-
其本质是包装了:
-
所属 UClass*(哪个 AttributeSet)
属性名 FName
C++ 成员指针
并通过反射方式获取对应的 FGameplayAttributeData& 实例。
五、属性变更流程
属性变更路径(以 GE 加血为例):
- GE 的 FModifierSpec 指定修改 Health
- GE 应用到目标 ASC → 调用 ApplyModToAttribute()
- 找到 AttributeSet → 定位到 FGameplayAttributeData
- 修改 CurrentValue → 触发 PostGameplayEffectExecute()
若是网络游戏,调用 OnRep_Health() 同步客户端
void UAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
if (Data.Attribute == GetHealthAttribute()) {
if (Health.GetCurrentValue() <= 0.f) {
// 死亡逻辑
}
}
}
六、属性计算流程
GAS 支持多个层级的属性修改:
层级 | 修改函数 | 特性 |
---|---|---|
Base | SetBaseAttributeValue | 基础值 |
Current | ApplyModToAttribute | 当前值 |
额外运算 | Execution Calculation | 动态伤害、暴击等 |
在执行 GE 时,会通过 FAggregator 系统完成对属性的加权合并运算:
float NewValue = BaseValue
+ AdditiveModifiers
+ MultiplicativeModifiers
+ Override (if any);
这些 Modifier 都存储在 ASC 的内部结构 FAggregatorMap 中,并会在任意值变动时自动重新计算属性。
七、属性网络同步原理
GAS 的属性同步机制非常高效:
- 使用 NetDeltaSerialize + FGameplayAttributeData
每个属性结构都注册了 Delta 序列化:
cpp
复制
编辑
void FGameplayAttributeData::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
只有当属性值变动时,才会序列化发送
自动触发 OnRep_属性名() 回调
- GAS 会为所有 AttributeSet 注册为子对象并同步
在 ASC 初始化时会自动注册属性集合并设置 RepNotify:
cpp
复制
编辑
ASC->AddReplicatedSubObject(AttributeSetInstance);
八、属性保护机制(只通过 GE 修改)
-
GAS 强烈建议:
-
不要在游戏逻辑中直接 Health = xxx
必须通过 ASC 和 GE 修改属性!
否则:
-
不会触发 OnRep
不会触发 PostGameplayEffectExecute
不参与网络同步
推荐方法:
FGameplayEffectSpecHandle Spec = MakeOutgoingSpec(HealthRestoreGE, 1.0f, Context);
ASC->ApplyGameplayEffectSpecToSelf(*Spec.Data.Get());
九、案例:实现护甲减伤
- 定义两个属性:Health, Armor
- 创建一个 Execution Calculation 类,逻辑如下:
float Damage = InDamage;
float Armor = 0.f;
ExecParams.AttemptCalculateCapturedAttributeMagnitude(ArmorDef, EvalParams, Armor);
Damage *= (1 - Armor * 0.01f); // 减伤公式
- 设置 OutSpec.AddOutputModifier() 作用到 Health 属性上
总结
模块 | 作用 | 说明 |
---|---|---|
UAttributeSet | 属性定义类 | 包含各个 FGameplayAttributeData |
FGameplayAttributeData | 属性值结构 | 有 Base 和 Current 值,支持序列化 |
FGameplayAttribute | 泛型引用器 | 可用于在运行时访问属性 |
ASC | 执行属性修改 | 持有属性容器、同步、处理 GE |