总览
如果说 Ability 表达“我要做什么”,GameplayEffect 表达的就是“我对状态做了什么”。GAS 里属性修改、冷却、消耗、Buff、Debuff、免疫、授予能力、阻塞能力、Cue 触发,最终大多会落到 GE 或 GE Component。
本篇重点拆开三个概念:UGameplayEffect 是设计时资产,FGameplayEffectSpec 是本次应用的数据包,FActiveGameplayEffect 是 ASC 上的运行时持续状态。分不清这三层,后面写 SetByCaller、堆叠、预测和移除都会混乱。
源码依据
关键文件:
GameplayEffect.hGameplayEffectTypes.hGameplayEffectComponent.hAbilitySystemComponent.cpp
UE5.8 源码注释明确说:GE 是资产模板,运行时应视为不可变;Spec 是运行时包装,携带 Level、Context、SetByCaller、捕获属性和动态标签;持续效果进入 Active Gameplay Effects 容器。
AbilitySystemComponent.cpp 里的 ApplyGameplayEffectSpecToSelf 会做这些事:
- 检查 Authority 或 PredictionKey。
- 拒绝不该预测的周期效果。
- 检查 Application Query 和
CanApply。 - 验证 Modifier 属性有效性。
- 预测 Instant GE 时临时按 Infinite 处理。
- 持续效果加入 ActiveGameplayEffects。
- Instant 效果立即 Execute。
- 触发 GameplayCue。
- 调用 GE 的
OnApplied。
Asset、Spec、ActiveEffect
| 层级 | 类型 | 生命周期 | 作用 |
|---|---|---|---|
| 资产模板 | UGameplayEffect |
编辑器资产/CDO | 定义 Duration、Modifier、Execution、Cue、Component |
| 应用数据 | FGameplayEffectSpec |
一次施放 | 携带 Level、Context、SetByCaller、动态 Tag |
| 活跃状态 | FActiveGameplayEffect |
ASC 上持续存在 | 管理剩余时间、Stack、复制、移除 |
工程上要避免在 GE 资产上保存运行时数据。比如本次火球伤害是 120,不应该写进 GE CDO,而应该写进 Spec 的 SetByCaller。
FGameplayEffectSpecHandle SpecHandle = ASC->MakeOutgoingSpec(DamageEffect, AbilityLevel, ASC->MakeEffectContext());
if (SpecHandle.IsValid())
{
static const FGameplayTag DamageTag = FGameplayTag::RequestGameplayTag(TEXT("Data.Damage"));
SpecHandle.Data->SetSetByCallerMagnitude(DamageTag, RuntimeDamage);
ASC->ApplyGameplayEffectSpecToTarget(*SpecHandle.Data.Get(), TargetASC);
}
Duration Policy
| 类型 | 运行时行为 | 适合 |
|---|---|---|
| Instant | 立即执行,通常修改 BaseValue | 伤害、治疗、资源消耗 |
| Duration | 进入 ActiveGameplayEffects,到时移除 | 眩晕、加速、护盾 |
| Infinite | 进入 ActiveGameplayEffects,直到主动移除 | 装备被动、光环、形态 |
Periodic 是持续 GE 的附加行为:每隔 Period 执行一次。源码里明确周期效果不预测,所以燃烧、流血、持续治疗这类效果要按服务器权威设计 UI 和表现。
Modifier 与聚合
GE Modifier 的操作类型来自 EGameplayModOp。源码里的计算模型不是简单从上到下依次执行,而是把 Add、Multiply、Divide、Override 等通道聚合后算最终值。
| 操作 | 用途 | 注意 |
|---|---|---|
| Additive | 加减固定值 | 最常用,伤害和治疗常见 |
| Multiplicitive | 百分比修饰 | 多个乘法修饰通常先聚合 |
| Division | 除法修饰 | 注意除零和数值设计 |
| Override | 覆盖属性 | 强控制状态谨慎使用 |
数值策划要知道:两个同通道百分比加成通常不是逐个连乘。比如 10% 和 30% 可能按同通道聚合为 40%,而不是 43%。
GameplayEffectComponent
UE5.8 延续了 GE Component 的方向。旧版 GE 里许多单体字段已经有 deprecated 路径,新项目更推荐用组件组合 GE 行为。
常见组件:
| 组件 | 作用 |
|---|---|
UAbilitiesGameplayEffectComponent |
GE 存在期间授予 Ability |
UAdditionalEffectsGameplayEffectComponent |
应用附加 GE |
UAssetTagsGameplayEffectComponent |
给 GE 资产声明标签 |
UBlockAbilityTagsGameplayEffectComponent |
阻止某类 Ability |
UCancelAbilityTagsGameplayEffectComponent |
应用后取消某类 Ability |
UChanceToApplyGameplayEffectComponent |
概率应用 |
UImmunityGameplayEffectComponent |
免疫规则 |
UTargetTagsGameplayEffectComponent |
给目标授予标签 |
UTargetTagRequirementsGameplayEffectComponent |
目标标签需求 |
GE Component 生活在 GE CDO/资产上,不应该保存每次执行的可变状态。如果需要运行时数据,放到 Spec、EffectContext、SetByCaller 或目标 ASC 上。
堆叠设计
Stacking 是 Buff/Debuff 系统的核心。设计时要回答:
- Stack 上限是多少。
- 新 Stack 是刷新 Duration,还是独立计时。
- Stack 溢出是拒绝、刷新、触发额外效果,还是替换旧 Stack。
- Stack 变化是否触发 Cue。
- UI 显示 Stack 数量还是只显示剩余时间。
不要等策划开始加“中毒可叠 5 层,满层爆炸”时才临时补字段。GE 资产一开始就应该按 Stack 规则分类。
项目落地分类
推荐按语义组织 GE:
| 前缀 | 示例 | 说明 |
|---|---|---|
GE_Init_ |
GE_Init_HeroAttributes |
初始化属性 |
GE_Cost_ |
GE_Cost_ManaSmall |
Ability 消耗 |
GE_Cooldown_ |
GE_Cooldown_Fireball |
冷却标签 |
GE_Damage_ |
GE_Damage_Fire |
伤害 |
GE_Buff_ |
GE_Buff_Haste |
正面状态 |
GE_Debuff_ |
GE_Debuff_Burning |
负面状态 |
GE_Passive_ |
GE_Passive_SwordMastery |
装备或天赋被动 |
每个 GE 资产都应该能回答:它改哪些属性、授予哪些标签、触发哪些 Cue、持续多久、是否可预测、谁能移除。
常见坑
- 把运行时数值写进 GE 资产。 应使用 Spec 的 SetByCaller 或 EffectContext。
- Instant 和 Duration 混用。 永久扣血用 Instant,临时加速用 Duration。
- Periodic 想做客户端预测。 源码明确不预测周期效果。
- GE 资产过度复用。 一个万能 GE 靠十几个 SetByCaller 控制,会让调试和内容审查变差。
- 忽略 GE Component。 新项目还按旧字段堆逻辑,会越来越偏离 5.8 的演进方向。
- 不写移除策略。 Infinite GE 必须知道谁移除、何时移除、断线或死亡时怎么处理。
本篇结论:GE 是 GAS 状态变化的标准通道。把 Asset、Spec、ActiveEffect 分清,再用 GE Component 组合行为,项目里的 Buff、冷却、消耗和被动才不会散成一堆特殊逻辑。
源码路径索引
Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemComponent.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cppEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cppEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/Abilities/GameplayAbility.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffect.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AttributeSet.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayPrediction.h