UE5 GAS 核心机制解析:GameplayEffect 属性修改原理(上)
引言
在深入理解 Unreal Engine 5 的 Gameplay Ability System(GAS)时,AttributeSet 解决了"属性如何存储与同步"的问题。而真正驱动这些属性变化的核心机制——谁来改、怎么改、何时生效——则由 GameplayEffect(GE)承担。 本文聚焦于 GE 中最基础也是最关键的模块:**Modifier 系统**及其运行时实例化流程。我们将从底层结构出发,剖析属性修改的计算逻辑、执行顺序以及不同类型 GE 的生命周期差异,为后续学习堆叠、执行计算和网络同步打下坚实基础。Modifier:属性变更的基本单元
每个 GameplayEffect 可包含多个FGameplayModifierInfo,代表一次独立的属性操作。其核心组成包括:
- Attribute:目标属性句柄
- ModifierOp:操作类型(加法、乘法等)
- ModifierMagnitude:数值来源配置
六种运算优先级规则如下:
((BaseValue + AddBase) * MultiplyAdditive / DivideAdditive * MultiplyCompound) + AddFinal其中 Override 操作会直接覆盖当前值,通常用于状态锁定类效果。
- AddBase:最先执行,累加至基础值
- MultiplyAdditive / DivideAdditive:分别对乘数/除数项求和后统一运算
- MultiplyCompound:逐个相乘,体现复合增益(如两个 50% 增伤叠加为 2.25 倍)
- AddFinal:最终附加修正
- Override:最后执行,强制设定结果
四种 Magnitude 计算方式
EGameplayEffectMagnitudeCalculation 定义了 Modifier 数值的生成策略:
1. ScalableFloat(查表缩放)
通过FScalableFloat 支持静态数值或曲线表查询,常用于技能等级关联伤害成长:
struct FScalableFloat {
float Value;
FCurveTableRowHandle CurveRow; // 按等级查表
};
该值在 Spec 创建阶段即被计算并缓存。
2. AttributeBased(属性依赖)
动态引用其他属性值进行计算,结构如下:
struct FAttributeBasedFloat {
FScalableFloat Coefficient;
FGameplayEffectAttributeCaptureDefinition BackingAttribute;
EAttributeBasedFloatCalculationType CalcType; // Base / Current / Bonus
};
典型用例:"治疗量 = 施法者攻击力 × 0.8"。是否快照(Snapshot)决定了是取施放时刻还是实时值。
3. CustomCalculation(自定义逻辑)
继承UGameplayModMagnitudeCalculation 实现复杂业务逻辑:
UCLASS(Abstract)
class UGameplayModMagnitudeCalculation : public UGameplayEffectCalculation {
virtual float CalculateBaseMagnitude(const FGameplayEffectSpec& Spec) const;
};
适用于基于环境、距离或多 Buff 状态的动态强度调整。
4. SetByCaller(调用传参)
运行时由外部代码注入数值,适合武器伤害绑定等场景:
struct FSetByCallerFloat {
FName DataName;
FGameplayTag DataTag;
};
需在创建 Spec 时调用 SetSetByCallerMagnitude 显式赋值。
运行时结构:从配置到执行
FModifierSpec:轻量化的运行时表示
相比配置阶段丰富的信息,运行时仅保留已计算的数值:
struct FModifierSpec {
private:
float EvaluatedMagnitude; // 已完成计算的最终值
};
此设计减少了内存占用,并避免重复计算。
聚合器绑定流程
对于持续型 GE,系统将 Modifier 注册至对应属性的聚合器中:
void FActiveGameplayEffectsContainer::AddModifierToAggregator(...) {
FAggregator* Agg = Owner->GetAggregator(ModInfo.Attribute);
FAggregatorMod Mod;
Mod.Op = ModInfo.ModifierOp;
Mod.EvaluatedMagnitude = ModSpec.GetEvaluatedMagnitude();
int32 Index = Agg->Modifiers.Add(Mod);
ActiveGE.ModifierHandles.Add(Index); // 存储索引以便移除
}
ModifierHandles 维护了每个 Modifier 在聚合器中的位置,便于后期精准删除。
属性求值链路
当访问属性 Current 值时,聚合器按 Op 类型分组执行运算:
float FAggregatorModChannel::ExecuteMod(float Base, const TArray<FAggregatorMod>& Mods) {
float Result = Base;
// 分组处理以保证一致性
float AddBaseSum = 0.f;
for (auto& M : Mods) if (M.Op == AddBase) AddBaseSum += M.Val;
Result += AddBaseSum;
float MulAddSum = 0.f;
for (auto& M : Mods) if (M.Op == MultiplyAdditive) MulAddSum += M.Val;
Result *= FMath::Max(MulAddSum, KINDA_SMALL_NUMBER);
// 其他类型依次处理...
return Result;
}
注意:MultiplyCompound 和 Override 采用逐项处理,因其结果受顺序影响。
DurationPolicy:三种生命周期模式
Instant(瞬发)
立即执行并退出,不进入激活列表:
if (GE->IsInstant()) {
ExecuteActiveEffects(Spec); // 直接修改 BaseValue
return FActiveGameplayEffectHandle(); // 无句柄返回
}
适用于一次性事件如伤害结算、资源消耗。
HasDuration(定时结束)
创建持久化实例,设置到期时间:ActiveGE->StartServerWorldTime = GetWorld()->GetTimeSeconds(); ActiveGE->EndServerWorldTime = ActiveGE->StartServerWorldTime + Duration; ActiveEffects.Add(ActiveGE);每帧通过
CheckActiveGameplayEffects 扫描超时项并自动清除。
Infinite(无限期)
行为类似 HasDuration,但无结束时间标记:ActiveGE->Duration = -1.f; // 表示永续 // 必须手动调用 RemoveActiveGameplayEffect 移除常用于永久增益或需条件触发解除的效果。
FGameplayEffectSpec:运行时上下文封装
FGameplayEffectSpec 是 GE 的实例化产物,关键字段包括:
- Def:指向原始 GE 资产
- Level:当前等级,影响 ScalableFloat 缩放
- EffectContext:携带施法者、目标、技能句柄等上下文
- Modifiers:已完成计算的 FModifierSpec 数组
Attribute Capture:跨实体属性读取
通过FGameplayEffectAttributeCaptureSpec 实现源/目标属性捕获:
void CaptureAttribute(const FGameplayEffectSpec& Spec) {
UAbilitySystemComponent* SourceASC = Spec.Context.GetInstigatorASC();
CapturedValue = Attribute.GetNumericValue(SourceASC);
bCaptured = true;
}
是否启用快照(bSnapshot)直接影响网络预测的一致性表现,将在高级主题中详述。
完整执行路径示意图
最小案例对比:Instant vs Duration
构建两个相同 Modifier 配置的 GE 进行测试:| 步骤 | Instant GE | Duration GE |
|---|---|---|
| Apply | 立即执行 ModifyBase | 注册 ActiveGE 并挂载聚合器 |
| 属性影响 | BaseValue 永久增加 | Current 动态合成,Base 不变 |
| 管理方式 | 无句柄,不可追踪 | 持有 Handle,支持查询剩余时间 |
| 移除机制 | N/A | 到期自动或手动调用 Remove |
| 回退效果 | 不会恢复 | 移除后 Current 自动还原 |
选择依据:
- Instant:永久性改动、消耗品使用
- Duration:临时 Buff、Debuff
- Infinite:需外部逻辑控制解除的长期状态
总结要点
- Modifier 是属性变更的原子单位,支持多种数值生成策略
- Spec 在创建时完成所有计算,提升运行效率
- 聚合器按 Op 类型分组执行,保障运算一致性
- 三种 DurationPolicy 决定了 GE 的生命周期与管理方式
- Attribute Capture 支持跨对象属性读取,是动态数值的基础
FGameplayEffectSpec::Create 和 ExecuteMod 的调用栈,加深对数据流的理解。